-
Notifications
You must be signed in to change notification settings - Fork 351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Import vocabulary annotations declared in external referenced models #1987
Changes from 5 commits
36af6c3
256e3b1
26a681e
d6087fc
9613de6
a3a8877
b838022
717346e
6ed8841
747511b
e4279cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -114,13 +114,14 @@ private CsdlSemanticsModel(CsdlModel referencedCsdlModel, IEdmDirectValueAnnotat | |||||
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) | ||||||
bool shouldAddTypes = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.Includes).Any(s => s.Namespace == schemaNamespace); | ||||||
IEnumerable<IEdmIncludeAnnotations> includeAnnotations = referencedCsdlModel.ParentModelReferences.SelectMany(s => s.IncludeAnnotations); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm concerned about the performance of executing this (somewhat expensive) SelectMany LINQ expression multiple times -- we execute it once on line 119, again on line 625, and then for each term on line 641. Probably be better, especially since we are iterating multiple times, to make this a list. removing the linq expression all together and simply iterating through building the collection would be even better, but probably not a significant savings here. |
||||||
bool shouldAddAnnotations = includeAnnotations.Any(); | ||||||
|
||||||
if (shouldAddTypes || shouldAddAnnotations) | ||||||
{ | ||||||
this.AddSchema(schema, false /*addAnnotations*/); | ||||||
this.AddSchema(schema, shouldAddTypes, shouldAddAnnotations, includeAnnotations); | ||||||
} | ||||||
|
||||||
// TODO: REF add annotations | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -522,15 +523,40 @@ private IEdmVocabularyAnnotation WrapVocabularyAnnotation(CsdlAnnotation annotat | |||||
ann => new CsdlSemanticsVocabularyAnnotation(schema, targetContext, annotationsContext, ann, qualifier)); | ||||||
} | ||||||
|
||||||
private void AddSchema(CsdlSchema schema) | ||||||
private void AddSchema(CsdlSchema schema, bool addSchemaElements = true) | ||||||
marabooy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
{ | ||||||
this.AddSchema(schema, true); | ||||||
this.AddSchema(schema, addSchemaElements, true, null); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I could use named parameters instead, like: this.AddSchema(schema, addSchemaElements, addAnnotations: true, includeAnnotations: null); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @habbes Use John's suggestion. Named parameters aren't supported in some c# versions. |
||||||
} | ||||||
|
||||||
private void AddSchema(CsdlSchema schema, bool addAnnotations) | ||||||
private void AddSchema(CsdlSchema schema, bool addSchemaElements, bool addAnnotations, IEnumerable<IEdmIncludeAnnotations> includeAnnotations = null) | ||||||
{ | ||||||
CsdlSemanticsSchema schemaWrapper = new CsdlSemanticsSchema(this, schema); | ||||||
this.schemata.Add(schemaWrapper); | ||||||
|
||||||
if (addSchemaElements) | ||||||
{ | ||||||
AddSchemaElements(schema, schemaWrapper); | ||||||
} | ||||||
|
||||||
if (!string.IsNullOrEmpty(schema.Alias)) | ||||||
{ | ||||||
this.SetNamespaceAlias(schema.Namespace, schema.Alias); | ||||||
} | ||||||
|
||||||
if (addAnnotations) | ||||||
{ | ||||||
AddOutOfLineAnnotationsFromSchema(schema, schemaWrapper, includeAnnotations); | ||||||
} | ||||||
|
||||||
var edmVersion = this.GetEdmVersion(); | ||||||
if (edmVersion == null || edmVersion < schema.Version) | ||||||
habbes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
{ | ||||||
this.SetEdmVersion(schema.Version); | ||||||
} | ||||||
} | ||||||
|
||||||
private void AddSchemaElements(CsdlSchema schema, CsdlSemanticsSchema schemaWrapper) | ||||||
{ | ||||||
foreach (IEdmSchemaType type in schemaWrapper.Types) | ||||||
{ | ||||||
CsdlSemanticsStructuredTypeDefinition structuredType = type as CsdlSemanticsStructuredTypeDefinition; | ||||||
|
@@ -574,34 +600,50 @@ private void AddSchema(CsdlSchema schema, bool addAnnotations) | |||||
{ | ||||||
RegisterElement(container); | ||||||
} | ||||||
} | ||||||
|
||||||
if (!string.IsNullOrEmpty(schema.Alias)) | ||||||
private void AddOutOfLineAnnotationsFromSchema(CsdlSchema schema, CsdlSemanticsSchema schemaWrapper, IEnumerable<IEdmIncludeAnnotations> includeAnnotations) | ||||||
{ | ||||||
foreach (CsdlAnnotations schemaOutOfLineAnnotations in schema.OutOfLineAnnotations) | ||||||
{ | ||||||
this.SetNamespaceAlias(schema.Namespace, schema.Alias); | ||||||
} | ||||||
string target = this.ReplaceAlias(schemaOutOfLineAnnotations.Target); | ||||||
|
||||||
if (addAnnotations) | ||||||
{ | ||||||
foreach (CsdlAnnotations schemaOutOfLineAnnotations in schema.OutOfLineAnnotations) | ||||||
List<CsdlSemanticsAnnotations> annotations; | ||||||
if (!this.outOfLineAnnotations.TryGetValue(target, out annotations)) | ||||||
{ | ||||||
string target = this.ReplaceAlias(schemaOutOfLineAnnotations.Target); | ||||||
|
||||||
List<CsdlSemanticsAnnotations> annotations; | ||||||
if (!this.outOfLineAnnotations.TryGetValue(target, out annotations)) | ||||||
{ | ||||||
annotations = new List<CsdlSemanticsAnnotations>(); | ||||||
this.outOfLineAnnotations[target] = annotations; | ||||||
} | ||||||
|
||||||
annotations.Add(new CsdlSemanticsAnnotations(schemaWrapper, schemaOutOfLineAnnotations)); | ||||||
annotations = new List<CsdlSemanticsAnnotations>(); | ||||||
this.outOfLineAnnotations[target] = annotations; | ||||||
} | ||||||
|
||||||
CsdlAnnotations filteredAnnotations = FilterIncludedAnnotations(schemaOutOfLineAnnotations, target, includeAnnotations); | ||||||
annotations.Add(new CsdlSemanticsAnnotations(schemaWrapper, filteredAnnotations)); | ||||||
} | ||||||
} | ||||||
|
||||||
var edmVersion = this.GetEdmVersion(); | ||||||
if (edmVersion == null || edmVersion < schema.Version) | ||||||
private CsdlAnnotations FilterIncludedAnnotations(CsdlAnnotations csdlAnnotations, string target, IEnumerable<IEdmIncludeAnnotations> includeAnnotations) | ||||||
{ | ||||||
if (includeAnnotations == null || !includeAnnotations.Any()) | ||||||
habbes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
{ | ||||||
this.SetEdmVersion(schema.Version); | ||||||
return csdlAnnotations; | ||||||
} | ||||||
|
||||||
var filteredAnnotations = csdlAnnotations.Annotations.Where(annotation => | ||||||
ShouldIncludeAnnotation(annotation, target, includeAnnotations)); | ||||||
return new CsdlAnnotations(filteredAnnotations, csdlAnnotations.Target, csdlAnnotations.Qualifier); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
should this use csdlAnnotations.Target, or "target" (which has the alias already resolved)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The aim here is to return a copy of the |
||||||
} | ||||||
|
||||||
private bool ShouldIncludeAnnotation(CsdlAnnotation annotation, string target, IEnumerable<IEdmIncludeAnnotations> includeAnnotations) | ||||||
{ | ||||||
// include annotation if it matches one of the edmx:IncludeAnnotation references | ||||||
// we check if the namespace matches, and (if applicable) also the target namespace and the qualifier | ||||||
// see: http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_IncludedAnnotations | ||||||
|
||||||
return includeAnnotations.Any(include => | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
LINQ is expensive -- AGS has gone through a LINQ hunt and removed all use of LINQ from their code. We should avoid it where we can. Instead, it would be more efficient to iterate through the collection and evaluate the options. |
||||||
(this.ReplaceAlias(annotation.Term).StartsWith(include.TermNamespace, System.StringComparison.Ordinal)) | ||||||
&& (string.IsNullOrEmpty(include.Qualifier) || annotation.Qualifier == include.Qualifier) | ||||||
&& (string.IsNullOrEmpty(include.TargetNamespace) | ||||||
|| target.StartsWith(include.TargetNamespace, System.StringComparison.Ordinal)) | ||||||
habbes marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1230,5 +1230,242 @@ public void ValidateEdmxVersions(string odataVersion) | |
Assert.Empty(edmErrors); | ||
Assert.Equal(edmModel.GetEdmVersion(), odataVersion == "4.0" ? EdmConstants.EdmVersion4 : EdmConstants.EdmVersion401); | ||
} | ||
|
||
[Fact] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Do you have any tests that validate referenced types (not annotations) from referenced namespace. For example, in your tests, you could test loading permissionsCsdl and validating that it imported types from "Default" and "Exmaple.Types" from main.csdl, but didn't import types from main.csdl defined in 3rd namespace. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added the test. |
||
public void ImportsAnnotationsFromExernalReferencedModel() | ||
{ | ||
string permissionsCsdl = @"<?xml version=""1.0"" encoding=""utf-8""?> | ||
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> | ||
<edmx:Reference Uri=""SimpleModel.xml""> | ||
<edmx:Include Namespace=""Default""/> | ||
<edmx:Include Namespace=""Example.Types"" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a test where the annotation schema defines an alias for the referenced namespace that is used locally in specifying the target, and make sure that annotations restricted to that target are still (exclusively) included. i.e., define an alias for the "Example.Types" namespace in permissions.Csdl (say "examples") and use it in the Annotations Target, and then in SimpleModel.xml specify a TargetNamespace of "Example.Types" in your include annotations and make sure it still returns the right set of annotations (I think your code handles that, just want to make sure it's in the tests...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added the test |
||
</edmx:Reference > | ||
<edmx:DataServices> | ||
<Schema Namespace=""Example.Permissions"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<EntityType Name=""BasicType""> | ||
<Key> | ||
<PropertyRef Name=""Id"" /> | ||
</Key> | ||
<Property Name=""Id"" Type=""Edm.Int32"" Nullable=""false"" /> | ||
</EntityType> | ||
<Annotations Target=""Default.Container/Products""> | ||
<Annotation Term=""NotIncludedNS.MyAnnotationPathTerm"" AnnotationPath=""abc/efg"" /> | ||
<Annotation Term=""Org.OData.Capabilities.V1.InsertRestrictions""> | ||
<Record> | ||
<PropertyValue Property=""Permissions""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""SchemeName"" String=""Scheme"" /> | ||
<PropertyValue Property=""Scopes""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""Scope"" String=""Product.Create"" /> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Annotation> | ||
<Annotation Term=""Org.OData.Capabilities.V1.DeleteRestrictions""> | ||
<Record> | ||
<PropertyValue Property=""Permissions""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""SchemeName"" String=""Scheme"" /> | ||
<PropertyValue Property=""Scopes""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""Scope"" String=""Product.Delete"" /> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Annotation> | ||
</Annotations> | ||
</Schema> | ||
</edmx:DataServices> | ||
</edmx:Edmx> | ||
"; | ||
string mainCsdl = @"<?xml version=""1.0"" encoding=""utf-8""?> | ||
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> | ||
<edmx:Reference Uri=""SimplePermissions.xml""> | ||
<edmx:IncludeAnnotations TermNamespace=""Org.OData.Capabilities.V1""/> | ||
</edmx:Reference > | ||
<edmx:DataServices> | ||
<Schema Namespace=""Example.Types"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<EntityType Name=""Product""> | ||
<Key> | ||
<PropertyRef Name=""Id"" /> | ||
</Key> | ||
<Property Name=""Id"" Type=""Edm.Int32"" Nullable=""false"" /> | ||
<Property Name=""Name"" Type=""Edm.String"" /> | ||
<Property Name=""Price"" Type=""Edm.Int32"" Nullable=""false"" /> | ||
</EntityType> | ||
</Schema> | ||
<Schema Namespace=""Default"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<EntityContainer Name=""Container""> | ||
<EntitySet Name=""Products"" EntityType=""Example.Types.Product"" /> | ||
</EntityContainer> | ||
</Schema> | ||
</edmx:DataServices> | ||
</edmx:Edmx> | ||
"; | ||
|
||
Func<Uri, XmlReader> getReferenceModelReader = uri => | ||
{ | ||
string csdl = uri.OriginalString == "SimplePermissions.xml" ? permissionsCsdl : mainCsdl; | ||
var stringReader = new System.IO.StringReader(permissionsCsdl); | ||
habbes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return XmlReader.Create(stringReader); | ||
}; | ||
|
||
|
||
var reader = XmlReader.Create(new System.IO.StringReader(mainCsdl)); | ||
IEdmModel model = CsdlReader.Parse(reader, getReferencedModelReaderFunc: getReferenceModelReader); | ||
|
||
var entitySet = model.FindDeclaredEntitySet("Products"); | ||
var annotations = model.FindVocabularyAnnotations(entitySet); | ||
Assert.Equal(2, annotations.Count()); | ||
|
||
// only imports annotations terms in the Org.OData.Capabilities.V1 namespace | ||
IEdmVocabularyAnnotation insertRestrictions = annotations.FirstOrDefault(a => a.Term.Name == "InsertRestrictions"); | ||
Assert.NotNull(insertRestrictions); | ||
IEdmVocabularyAnnotation deleteRestrictions = annotations.FirstOrDefault(a => a.Term.Name == "DeleteRestrictions"); | ||
Assert.NotNull(deleteRestrictions); | ||
|
||
IEdmRecordExpression record = insertRestrictions.Value as IEdmRecordExpression; | ||
Assert.NotNull(record); | ||
var insertPermissions = record.FindProperty("Permissions").Value as IEdmCollectionExpression; | ||
Assert.NotNull(insertPermissions); | ||
|
||
var permission = insertPermissions.Elements.FirstOrDefault() as IEdmRecordExpression; | ||
Assert.NotNull(permission); | ||
var scheme = permission.FindProperty("SchemeName"); | ||
Assert.NotNull(scheme); | ||
Assert.Equal("Scheme", ((IEdmStringConstantExpression)scheme.Value).Value); | ||
|
||
// do not import other schema elements since there's | ||
// no explicit <edmx:Include Namespace="Example.Permissions" /> declaration | ||
var basicType = model.FindType("Example.Permissions.BasicType"); | ||
Assert.Null(basicType); | ||
} | ||
|
||
[Fact] | ||
public void ImportsAnnotationsFromReferencedModelsOnlyIfTargetNamespaceAndQualifierMatch() | ||
{ | ||
string permissionsCsdl = @"<?xml version=""1.0"" encoding=""utf-8""?> | ||
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> | ||
<edmx:Reference Uri=""SimpleModel.xml""> | ||
<edmx:Include Namespace=""Default""/> | ||
<edmx:Include Namespace=""Example.Types"" /> | ||
</edmx:Reference > | ||
<edmx:DataServices> | ||
<Schema Namespace=""Example.Permissions"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<Annotations Target=""Example.Types.Product""> | ||
<Annotation Term=""OtherIncludedNS.SomeAnnotationPath"" AnnotationPath=""abc/efg"" /> | ||
</Annotations> | ||
<Annotations Target=""Default.Container/Products""> | ||
<Annotation Term=""OtherIncludedNS.SomeAnnotationPath"" AnnotationPath=""abc/efg"" /> | ||
<Annotation Term=""NotIncludedNS.MyAnnotationPathTerm"" AnnotationPath=""abc/efg"" /> | ||
<Annotation Term=""Org.OData.Capabilities.V1.InsertRestrictions"" Qualifier=""Insert""> | ||
<Record> | ||
<PropertyValue Property=""Permissions""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""SchemeName"" String=""Scheme"" /> | ||
<PropertyValue Property=""Scopes""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""Scope"" String=""Product.Create"" /> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Annotation> | ||
<Annotation Term=""Org.OData.Capabilities.V1.DeleteRestrictions""> | ||
<Record> | ||
<PropertyValue Property=""Permissions""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""SchemeName"" String=""Scheme"" /> | ||
<PropertyValue Property=""Scopes""> | ||
<Collection> | ||
<Record> | ||
<PropertyValue Property=""Scope"" String=""Product.Delete"" /> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Collection> | ||
</PropertyValue> | ||
</Record> | ||
</Annotation> | ||
</Annotations> | ||
</Schema> | ||
</edmx:DataServices> | ||
</edmx:Edmx> | ||
"; | ||
string mainCsdl = @"<?xml version=""1.0"" encoding=""utf-8""?> | ||
<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx""> | ||
<edmx:Reference Uri=""SimplePermissions.xml""> | ||
<edmx:IncludeAnnotations TermNamespace=""Org.OData.Capabilities.V1"" Qualifier=""Insert""/> | ||
<edmx:IncludeAnnotations TermNamespace=""OtherIncludedNS"" TargetNamespace=""Example.Types""/> | ||
</edmx:Reference > | ||
<edmx:DataServices> | ||
<Schema Namespace=""Example.Types"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<EntityType Name=""Product""> | ||
<Key> | ||
<PropertyRef Name=""Id"" /> | ||
</Key> | ||
<Property Name=""Id"" Type=""Edm.Int32"" Nullable=""false"" /> | ||
<Property Name=""Name"" Type=""Edm.String"" /> | ||
<Property Name=""Price"" Type=""Edm.Int32"" Nullable=""false"" /> | ||
</EntityType> | ||
</Schema> | ||
<Schema Namespace=""Default"" xmlns=""http://docs.oasis-open.org/odata/ns/edm""> | ||
<EntityContainer Name=""Container""> | ||
<EntitySet Name=""Products"" EntityType=""ODataAuthorizationDemo.Models.Product"" /> | ||
</EntityContainer> | ||
</Schema> | ||
</edmx:DataServices> | ||
</edmx:Edmx> | ||
"; | ||
|
||
Func<Uri, XmlReader> getReferenceModelReader = uri => | ||
{ | ||
string csdl = uri.OriginalString == "SimplePermissions.xml" ? permissionsCsdl : mainCsdl; | ||
var stringReader = new System.IO.StringReader(permissionsCsdl); | ||
return XmlReader.Create(stringReader); | ||
}; | ||
|
||
|
||
var reader = XmlReader.Create(new System.IO.StringReader(mainCsdl)); | ||
IEdmModel model = CsdlReader.Parse(reader, getReferencedModelReaderFunc: getReferenceModelReader); | ||
|
||
var entitySet = model.FindDeclaredEntitySet("Products"); | ||
var entitySetAnnotations = model.FindVocabularyAnnotations(entitySet); | ||
Assert.Single(entitySetAnnotations); | ||
|
||
|
||
// imports annotation terms in the Org.OData.Capabilities.V1 namespace that match the qualifier Insert | ||
IEdmVocabularyAnnotation insertRestrictions = entitySetAnnotations.FirstOrDefault(a => a.Term.Name == "InsertRestrictions"); | ||
Assert.NotNull(insertRestrictions); | ||
|
||
// imports annotation terms in the OtherIncludedNS namespace that match the target namespace Example.Types | ||
var product = model.FindDeclaredType("Example.Types.Product"); | ||
var productAnnotations = model.FindVocabularyAnnotations(product); | ||
Assert.Single(productAnnotations); | ||
IEdmVocabularyAnnotation productAnnotation = productAnnotations.FirstOrDefault(a => a.Term.Name == "SomeAnnotationPath"); | ||
Assert.NotNull(productAnnotation); | ||
} | ||
|
||
|
||
} | ||
} | ||
habbes marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.