http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/code/ReflectionDataContext.cs ---------------------------------------------------------------------- diff --git a/tests/code/ReflectionDataContext.cs b/tests/code/ReflectionDataContext.cs new file mode 100644 index 0000000..1635d54 --- /dev/null +++ b/tests/code/ReflectionDataContext.cs @@ -0,0 +1,743 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +namespace DataJS.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using Microsoft.OData.Service; + using System.Globalization; + using System.Linq; + using System.Reflection; + + /// <summary> + /// Provides a reflection-based, updatable data context. + /// </summary> + public abstract class ReflectionDataContext + { + // Fields + private List<object> deletedObjects = new List<object>(); + private List<Action> pendingChanges; + private static Dictionary<Type, Dictionary<string, IList>> resourceSetsByContextTypeStorage = new Dictionary<Type, Dictionary<string, IList>>(); + + // Methods + protected ReflectionDataContext() + { + this.MetadataHelper = new ReflectionMetadataHelper(this); + this.pendingChanges = new List<Action>(); + if (!resourceSetsByContextTypeStorage.ContainsKey(base.GetType())) + { + resourceSetsByContextTypeStorage.Add(base.GetType(), new Dictionary<string, IList>()); + foreach (string resourceSetName in this.MetadataHelper.GetResourceSetNames()) + { + Type resourceType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName); + IList listOfTInstance = Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { resourceType })) as IList; + this.ResourceSetsStorage.Add(resourceSetName, listOfTInstance); + } + } + this.EnsureDataIsInitialized(); + } + + public virtual void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName"); + ExceptionUtilities.CheckArgumentNotNull(resourceToBeAdded, "resourceToBeAdded"); + UpdatableToken targetToken = UpdatableToken.AssertIsToken(targetResource, "targetResource"); + targetResource = targetToken.Resource; + resourceToBeAdded = UpdatableToken.AssertIsTokenAndResolve(resourceToBeAdded, "resourceToBeAdded"); + IList list = this.GetValue(targetToken, propertyName) as IList; + ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name }); + this.pendingChanges.Add(delegate { + list.Add(resourceToBeAdded); + }); + } + + public virtual void ClearChanges() + { + this.pendingChanges.Clear(); + } + + public void ClearData() + { + this.ResourceSetsStorage.Clear(); + } + + private static bool CompareETagValues(Dictionary<string, object> resourceCookieValues, IEnumerable<KeyValuePair<string, object>> concurrencyValues) + { + if (concurrencyValues.Count<KeyValuePair<string, object>>() != resourceCookieValues.Count) + { + return false; + } + foreach (KeyValuePair<string, object> keyValuePair in concurrencyValues) + { + if (!resourceCookieValues.ContainsKey(keyValuePair.Key)) + { + return false; + } + if (keyValuePair.Value == null) + { + return (resourceCookieValues[keyValuePair.Key] == null); + } + if (!keyValuePair.Value.Equals(resourceCookieValues[keyValuePair.Key])) + { + return false; + } + } + return true; + } + + public virtual object CreateResource(string containerName, string fullTypeName) + { + ExceptionUtilities.CheckArgumentNotNull(fullTypeName, "fullTypeName"); + UpdatableToken token = this.InstantiateResourceType(fullTypeName); + if (containerName != null) + { + this.pendingChanges.Add(delegate { + this.GetResourceSetEntities(containerName).Add(token.Resource); + }); + } + return token; + } + + private void DeleteAllReferences(object targetResource) + { + foreach (string currentSetName in this.MetadataHelper.GetResourceSetNames()) + { + Type currentEntityType = this.MetadataHelper.GetResourceTypeOfSet(currentSetName); + IList entitySetList = this.GetResourceSetEntities(currentSetName); + foreach (NavigationPropertyInfo navigationProperty in this.MetadataHelper.GetNavigationProperties(GetResourceTypeFullName(currentEntityType))) + { + if (navigationProperty.CollectionElementType != null) + { + foreach (object currentEntityInstance in entitySetList) + { + this.RemoveResourceFromCollectionOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance); + } + } + else + { + ExceptionUtilities.CheckObjectNotNull(navigationProperty.PropertyInfo, "Invalid navigation property info", new object[0]); + foreach (object currentEntityInstance in entitySetList) + { + this.SetEntityReferenceToNullOnTargetResourceMatch(targetResource, navigationProperty, currentEntityInstance); + } + } + } + } + } + + public virtual void DeleteResource(object targetResource) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + targetResource = UpdatableToken.AssertIsTokenAndResolve(targetResource, "targetResource"); + string resourceSetName = this.GetResourceSetOfTargetResource(targetResource); + ExceptionUtilities.CheckObjectNotNull(resourceSetName, "Unable to find set of the resource to delete", new object[0]); + this.deletedObjects.Add(targetResource); + IList resourceSetList = this.GetResourceSetEntities(resourceSetName); + this.DeleteAllReferences(targetResource); + this.pendingChanges.Add(delegate { + resourceSetList.Remove(targetResource); + }); + } + + protected abstract void EnsureDataIsInitialized(); + + protected virtual Type GetCollectionPropertyType(string fullTypeName, string propertyName) + { + Type type = this.MetadataHelper.FindClrTypeByFullName(fullTypeName); + Type collectionType = null; + if (type != null) + { + PropertyInfo property = type.GetProperty(propertyName); + if (property != null) + { + collectionType = property.PropertyType; + } + } + return collectionType; + } + + private Dictionary<string, object> GetConcurrencyValues(object targetResource) + { + Dictionary<string, object> etagValues = new Dictionary<string, object>(); + foreach (string etagProperty in this.MetadataHelper.GetETagPropertiesOfType(GetResourceTypeFullName(targetResource.GetType()))) + { + etagValues.Add(etagProperty, targetResource.GetType().GetProperty(etagProperty).GetValue(targetResource, null)); + } + return etagValues; + } + + public virtual object GetResource(IQueryable query, string fullTypeName) + { + ExceptionUtilities.CheckArgumentNotNull(query, "query"); + object resource = null; + foreach (object r in query) + { + ExceptionUtilities.Assert(resource == null, "Invalid Uri specified. The query '{0}' must refer to a single resource", new object[] { query.ToString() }); + resource = r; + } + if (resource == null) + { + return null; + } + if (fullTypeName != null) + { + this.ValidateResourceType(resource, fullTypeName); + } + return new UpdatableToken(resource); + } + + public IList<T> GetResourceSetEntities<T>(string resourceSetName) + { + return (IList<T>) this.GetResourceSetEntities(resourceSetName); + } + + internal IList GetResourceSetEntities(string resourceSetName) + { + IList entities; + if (!this.ResourceSetsStorage.TryGetValue(resourceSetName, out entities)) + { + Type elementType = this.MetadataHelper.GetResourceTypeOfSet(resourceSetName); + entities = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { elementType })); + this.ResourceSetsStorage[resourceSetName] = entities; + } + return entities; + } + + private string GetResourceSetOfTargetResource(object targetResource) + { + foreach (string currentResourceSetName in this.MetadataHelper.GetResourceSetNames()) + { + if (this.GetResourceSetEntities(currentResourceSetName).Contains(targetResource)) + { + return currentResourceSetName; + } + } + return null; + } + + public static string GetResourceTypeFullName(Type type) + { + return type.FullName.Replace('+', '_'); + } + + public virtual object GetValue(object targetResource, string propertyName) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName"); + UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource"); + if (token.PendingPropertyUpdates.ContainsKey(propertyName)) + { + return token.PendingPropertyUpdates[propertyName]; + } + targetResource = token.Resource; + PropertyInfo pi = targetResource.GetType().GetProperty(propertyName); + ExceptionUtilities.CheckObjectNotNull(pi, "Cannot find the property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name }); + object value = pi.GetValue(targetResource, null); + if ((value != null) && (pi.PropertyType.Assembly == base.GetType().Assembly)) + { + ExceptionUtilities.Assert(!this.MetadataHelper.IsTypeAnEntityType(pi.PropertyType), "GetValue should never be called for reference properties. Type was '{0}', property was '{1}'", new object[] { pi.PropertyType.FullName, propertyName }); + value = new UpdatableToken(value); + } + return value; + } + + private UpdatableToken InstantiateResourceType(string fullTypeName) + { + Type t = this.MetadataHelper.FindClrTypeByFullName(fullTypeName); + object instance = Activator.CreateInstance(t); + UpdatableToken token = new UpdatableToken(instance); + foreach (PropertyInfo p in t.GetProperties()) + { + object generatedValue; + PropertyInfo property = p; + if (this.IsCollectionProperty(property)) + { + Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(t), property.Name); + if (collectionType != null) + { + object newCollection = Activator.CreateInstance(collectionType); + token.PendingPropertyUpdates[property.Name] = newCollection; + this.pendingChanges.Add(delegate { + property.SetValue(instance, newCollection, null); + }); + } + } + if (this.TryGetStoreGeneratedValue(fullTypeName, property.Name, out generatedValue)) + { + token.PendingPropertyUpdates[property.Name] = generatedValue; + this.pendingChanges.Add(delegate { + property.SetValue(instance, generatedValue, null); + }); + } + } + return token; + } + + protected virtual bool IsCollectionProperty(PropertyInfo propertyInfo) + { + return ((typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && (propertyInfo.PropertyType != typeof(string))) && (propertyInfo.PropertyType != typeof(byte[]))); + } + + public virtual void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName"); + ExceptionUtilities.CheckArgumentNotNull(resourceToBeRemoved, "resourceToBeRemoved"); + UpdatableToken.AssertIsToken(targetResource, "targetResource"); + resourceToBeRemoved = UpdatableToken.AssertIsTokenAndResolve(resourceToBeRemoved, "resourceToBeRemoved"); + IList list = this.GetValue(targetResource, propertyName) as IList; + ExceptionUtilities.CheckObjectNotNull(list, "Property '{0}' on type '{1}' was not a list", new object[] { propertyName, targetResource.GetType().Name }); + this.pendingChanges.Add(delegate { + list.Remove(resourceToBeRemoved); + }); + } + + private void RemoveResourceFromCollectionOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance) + { + IEnumerable childCollectionObject = navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) as IEnumerable; + if (childCollectionObject.Cast<object>().Any<object>(delegate (object o) { + return o == targetResource; + })) + { + MethodInfo removeMethod = navigationPropertyInfo.PropertyInfo.PropertyType.GetMethod("Remove"); + this.pendingChanges.Add(delegate { + removeMethod.Invoke(childCollectionObject, new object[] { targetResource }); + }); + } + } + + public virtual object ResetResource(object resource) + { + ExceptionUtilities.CheckArgumentNotNull(resource, "resource"); + UpdatableToken token = UpdatableToken.AssertIsToken(resource, "resource"); + resource = token.Resource; + token = new UpdatableToken(resource); + object newInstance = Activator.CreateInstance(resource.GetType()); + ExceptionUtilities.CheckObjectNotNull(newInstance, "Cannot reset resource because unable to creating new instance of type '{0}' returns null", new object[] { resource.GetType().Name }); + foreach (string propertyToReset in this.MetadataHelper.GetPropertiesToReset(GetResourceTypeFullName(resource.GetType()))) + { + PropertyInfo pi = newInstance.GetType().GetProperty(propertyToReset); + ExceptionUtilities.CheckObjectNotNull(pi, "Cannot reset resource because unable to find property '{0}'", new object[] { propertyToReset }); + object newValue = pi.GetValue(newInstance, null); + this.pendingChanges.Add(delegate { + pi.SetValue(resource, newValue, null); + }); + token.PendingPropertyUpdates[propertyToReset] = newValue; + } + return token; + } + + public virtual object ResolveResource(object resource) + { + ExceptionUtilities.CheckArgumentNotNull(resource, "resource"); + return UpdatableToken.AssertIsTokenAndResolve(resource, "resource"); + } + + public virtual void SaveChanges() + { + foreach (Action pendingChange in this.pendingChanges) + { + pendingChange(); + } + this.pendingChanges.Clear(); + foreach (object deleted in this.deletedObjects) + { + foreach (object entity in this.ResourceSetsStorage.SelectMany<KeyValuePair<string, IList>, object>(delegate (KeyValuePair<string, IList> p) { + return p.Value.Cast<object>(); + })) + { + ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, entity), "Found deleted entity!", new object[0]); + foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties()) + { + object value = propertyInfo.GetValue(entity, null); + ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, value), "Found deleted entity!", new object[0]); + IEnumerable enumerable = value as IEnumerable; + if (enumerable != null) + { + foreach (object valueElement in enumerable.Cast<object>()) + { + ExceptionUtilities.Assert(!object.ReferenceEquals(deleted, valueElement), "Found deleted entity!", new object[0]); + } + } + } + } + } + this.deletedObjects.Clear(); + } + + protected virtual void SetCollectionPropertyValue(object targetResource, PropertyInfo propertyInfo, IEnumerable propertyValue) + { + object collection; + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyInfo, "propertyInfo"); + ExceptionUtilities.CheckArgumentNotNull(propertyValue, "propertyValue"); + Type collectionType = this.GetCollectionPropertyType(GetResourceTypeFullName(propertyInfo.ReflectedType), propertyInfo.Name); + ExceptionUtilities.CheckObjectNotNull(collectionType, "Could not infer collection type for property", new object[0]); + propertyValue = propertyValue.Cast<object>().Select<object, object>(delegate (object o) { + return UpdatableToken.ResolveIfToken(o); + }); + ConstructorInfo enumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable) }); + if (enumerableConstructor != null) + { + collection = enumerableConstructor.Invoke(new object[] { propertyValue }); + } + else if (collectionType.IsGenericType && (collectionType.GetGenericArguments().Count<Type>() == 1)) + { + Type typeArgument = collectionType.GetGenericArguments().Single<Type>(); + ConstructorInfo typedEnumerableConstructor = collectionType.GetConstructor(new Type[] { typeof(IEnumerable<>).MakeGenericType(new Type[] { typeArgument }) }); + if (typedEnumerableConstructor != null) + { + object typedEnumerable = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new Type[] { typeArgument }).Invoke(null, new object[] { propertyValue }); + collection = typedEnumerableConstructor.Invoke(new object[] { typedEnumerable }); + } + else + { + MethodInfo typedAddMethod = collectionType.GetMethod("Add", new Type[] { typeArgument }); + ExceptionUtilities.CheckObjectNotNull(typedAddMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]); + collection = Activator.CreateInstance(collectionType); + foreach (object element in propertyValue) + { + typedAddMethod.Invoke(collection, new object[] { element }); + } + } + } + else + { + MethodInfo addMethod = collectionType.GetMethod("Add"); + ExceptionUtilities.CheckObjectNotNull(addMethod, "Could not find constructor or add method for type: " + collectionType.FullName, new object[0]); + collection = Activator.CreateInstance(collectionType); + foreach (object element in propertyValue) + { + addMethod.Invoke(collection, new object[] { element }); + } + } + propertyInfo.SetValue(targetResource, collection, null); + } + + public virtual void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues) + { + ExceptionUtilities.CheckArgumentNotNull(resourceCookie, "resourceCookie"); + ExceptionUtilities.ThrowDataServiceExceptionIfFalse(checkForEquality.HasValue, 0x1a1, "Missing concurrency token for update operation", new object[0]); + ExceptionUtilities.Assert(checkForEquality.Value, "Should not be called with check for equality parameter equal to false", new object[0]); + ExceptionUtilities.CheckArgumentNotNull(concurrencyValues, "concurrencyValues"); + if (concurrencyValues.Any<KeyValuePair<string, object>>()) + { + resourceCookie = UpdatableToken.AssertIsTokenAndResolve(resourceCookie, "resourceCookie"); + ExceptionUtilities.ThrowDataServiceExceptionIfFalse(CompareETagValues(this.GetConcurrencyValues(resourceCookie), concurrencyValues), 0x19c, "Concurrency tokens do not match", new object[0]); + } + } + + private void SetEntityReferenceToNullOnTargetResourceMatch(object targetResource, NavigationPropertyInfo navigationPropertyInfo, object currentEntityInstance) + { + if (navigationPropertyInfo.PropertyInfo.GetValue(currentEntityInstance, null) == targetResource) + { + this.pendingChanges.Add(delegate { + navigationPropertyInfo.PropertyInfo.SetValue(currentEntityInstance, null, null); + }); + } + } + + public virtual void SetReference(object targetResource, string propertyName, object propertyValue) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName"); + if (propertyValue != null) + { + UpdatableToken.AssertIsToken(propertyValue, "propertyValue"); + } + this.SetValue(targetResource, propertyName, propertyValue); + } + + public virtual void SetValue(object targetResource, string propertyName, object propertyValue) + { + ExceptionUtilities.CheckArgumentNotNull(targetResource, "targetResource"); + ExceptionUtilities.CheckArgumentNotNull(propertyName, "propertyName"); + UpdatableToken token = UpdatableToken.AssertIsToken(targetResource, "targetResource"); + targetResource = token.Resource; + token.PendingPropertyUpdates[propertyName] = propertyValue; + this.pendingChanges.Add(delegate { + object generatedValue; + Type t = targetResource.GetType(); + PropertyInfo pi = t.GetProperty(propertyName); + ExceptionUtilities.CheckObjectNotNull(pi, "Unable to find property '{0}' on type '{1}'", new object[] { propertyName, targetResource.GetType().Name }); + if (this.TryGetStoreGeneratedValue(GetResourceTypeFullName(t), propertyName, out generatedValue)) + { + propertyValue = generatedValue; + } + if (this.IsCollectionProperty(pi)) + { + ExceptionUtilities.CheckObjectNotNull(propertyValue, "Collection property value was null", new object[0]); + IEnumerable enumerable = propertyValue as IEnumerable; + ExceptionUtilities.CheckObjectNotNull(enumerable, "Collection property value was not an enumerable", new object[0]); + this.SetCollectionPropertyValue(targetResource, pi, enumerable); + } + else + { + propertyValue = UpdatableToken.ResolveIfToken(propertyValue); + pi.SetValue(targetResource, propertyValue, null); + } + }); + } + + protected virtual bool TryGetStoreGeneratedValue(string fullTypeName, string propertyName, out object propertyValue) + { + propertyValue = null; + return false; + } + + private void ValidateResourceType(object targetResource, string fullTypeName) + { + ExceptionUtilities.Assert(this.MetadataHelper.FindClrTypeByFullName(fullTypeName).IsAssignableFrom(targetResource.GetType()), "Invalid uri specified. expected type: '{0}', actual type: '{1}'", new object[] { fullTypeName, targetResource.GetType().FullName }); + } + + // Properties + internal ReflectionMetadataHelper MetadataHelper { get; set; } + + internal Dictionary<string, IList> ResourceSetsStorage + { + get + { + Dictionary<string, IList> resourceSetsLookup = null; + Type currentContextType = base.GetType(); + ExceptionUtilities.Assert(resourceSetsByContextTypeStorage.TryGetValue(currentContextType, out resourceSetsLookup), "Cannot find resource sets by the context type '{0}'", new object[] { currentContextType }); + return resourceSetsLookup; + } + } + + #region Inner types. + + internal class ReflectionMetadataHelper + { + // Fields + private ReflectionDataContext reflectionDataContext; + + // Methods + public ReflectionMetadataHelper(ReflectionDataContext reflectionDataContext) + { + this.reflectionDataContext = reflectionDataContext; + } + + public Type FindClrTypeByFullName(string resourceTypeFullName) + { + Type type = this.reflectionDataContext.GetType().Assembly.GetTypes().Where<Type>(delegate (Type t) { + return (ReflectionDataContext.GetResourceTypeFullName(t) == resourceTypeFullName); + }).FirstOrDefault<Type>(); + ExceptionUtilities.CheckObjectNotNull(type, "Unable to find type '{0}'", new object[] { resourceTypeFullName }); + return type; + } + + public string[] GetETagPropertiesOfType(string fullTypeName) + { + Type type = this.FindClrTypeByFullName(fullTypeName); + List<string> etags = new List<string>(); + foreach (ETagAttribute customAttribute in type.GetCustomAttributes(typeof(ETagAttribute), true)) + { + etags.AddRange(customAttribute.PropertyNames); + } + + return etags.ToArray(); + } + + public string[] GetKeyProperties(string fullTypeName) + { + Type type = this.FindClrTypeByFullName(fullTypeName); + List<string> keyPropertyList = new List<string>(); + foreach (PropertyInfo keyProperty in type.GetProperties().Where(pi => pi.Name.Contains("ID"))) + { + keyPropertyList.Add(keyProperty.Name); + } + + return keyPropertyList.ToArray(); + } + + public NavigationPropertyInfo[] GetNavigationProperties(string fullTypeName) + { + Type type = this.FindClrTypeByFullName(fullTypeName); + var navigationProperties = new List<NavigationPropertyInfo>(); + var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName)); + foreach (PropertyInfo pi in type.GetProperties()) + { + if (!keyProperties.Contains(pi.Name)) + { + if (this.IsTypeAnEntityType(pi.PropertyType)) + { + navigationProperties.Add(new NavigationPropertyInfo(pi, null)); + } + + if (pi.PropertyType.IsGenericType && ((pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) || (pi.PropertyType.GetGenericTypeDefinition() == typeof(Collection<>)))) + { + Type elementType = pi.PropertyType.GetGenericArguments()[0]; + if (this.IsTypeAnEntityType(elementType)) + { + navigationProperties.Add(new NavigationPropertyInfo(pi, elementType)); + } + } + } + } + + return navigationProperties.ToArray(); + } + + public string[] GetPropertiesToReset(string fullTypeName) + { + Type type = this.FindClrTypeByFullName(fullTypeName); + var keyProperties = new List<string>(this.GetKeyProperties(fullTypeName)); + var navigationProperties = new List<string>(this.GetNavigationProperties(fullTypeName).Select(ni =>ni.PropertyInfo.Name)); + return type.GetProperties().Where( + pi => !keyProperties.Contains(pi.Name) && !navigationProperties.Contains(pi.Name) + ).Select(pi => pi.Name).ToArray(); + } + + public string[] GetResourceSetNames() + { + return this.reflectionDataContext.GetType().GetProperties().Where( + pi => pi.PropertyType.IsGenericType && (pi.PropertyType.GetGenericTypeDefinition() == typeof(IQueryable<>)) + ).Select(pi => pi.Name).ToArray(); + } + + public Type GetResourceTypeOfSet(string resourceSetName) + { + PropertyInfo resourceSetPropertyInfo = this.reflectionDataContext.GetType().GetProperties().Where(pi => pi.Name == resourceSetName).FirstOrDefault(); + ExceptionUtilities.CheckObjectNotNull(resourceSetPropertyInfo, "Error finding type of set '{0}'", new object[] { resourceSetName }); + return resourceSetPropertyInfo.PropertyType.GetGenericArguments()[0]; + } + + public bool IsTypeAnEntityType(Type t) + { + foreach (string setName in this.GetResourceSetNames()) + { + if (this.GetResourceTypeOfSet(setName).IsAssignableFrom(t)) + { + return true; + } + } + + return false; + } + } + + internal static class ExceptionUtilities + { + // Methods + public static void Assert(bool condition, string errorMessage, params object[] messageArguments) + { + if (!condition) + { + throw new InvalidOperationException("Assertion failed: " + string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments)); + } + } + + public static void CheckArgumentNotNull(object argument, string argumentName) + { + if (argument == null) + { + throw new ArgumentNullException(argumentName); + } + } + + public static void CheckCollectionNotEmpty<TElement>(IEnumerable<TElement> argument, string argumentName) + { + CheckArgumentNotNull(argument, argumentName); + if (!argument.Any<TElement>()) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Collection argument '{0}' must have at least one element.", new object[] { argumentName })); + } + } + + public static void CheckObjectNotNull(object value, string exceptionMessageFormatText, params object[] messageArguments) + { + Assert(exceptionMessageFormatText != null, "message cannnot be null", new object[0]); + Assert(messageArguments != null, "messageArguments cannnot be null", new object[0]); + if (value == null) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, exceptionMessageFormatText, messageArguments)); + } + } + + public static void ThrowDataServiceExceptionIfFalse(bool condition, int statusCode, string errorMessage, params object[] messageArguments) + { + if (!condition) + { + throw new DataServiceException(statusCode, string.Format(CultureInfo.InvariantCulture, errorMessage, messageArguments)); + } + } + } + + public class UpdatableToken + { + // Methods + public UpdatableToken(object resource) + { + ExceptionUtilities.CheckArgumentNotNull(resource, "resource"); + this.Resource = resource; + this.PendingPropertyUpdates = new Dictionary<string, object>(); + } + + public static UpdatableToken AssertIsToken(object resource, string name) + { + ExceptionUtilities.CheckArgumentNotNull(resource, "resource"); + UpdatableToken token = resource as UpdatableToken; + ExceptionUtilities.CheckObjectNotNull(token, "{0} was not a token. Type was: '{1}'", new object[] { name, resource.GetType() }); + return token; + } + + public static object AssertIsTokenAndResolve(object resource, string name) + { + return AssertIsToken(resource, name).Resource; + } + + public static object ResolveIfToken(object resource) + { + UpdatableToken token = resource as UpdatableToken; + if (token != null) + { + resource = token.Resource; + } + return resource; + } + + // Properties + public IDictionary<string, object> PendingPropertyUpdates { get; set; } + + public object Resource { get; set; } + } + + internal class NavigationPropertyInfo + { + // Methods + internal NavigationPropertyInfo(PropertyInfo pi, Type collectionElementType) + { + this.PropertyInfo = pi; + this.CollectionElementType = collectionElementType; + } + + // Properties + public Type CollectionElementType { get; set; } + + public PropertyInfo PropertyInfo { get; set; } + } + + #endregion Inner types. + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/code/csdlreader.cs ---------------------------------------------------------------------- diff --git a/tests/code/csdlreader.cs b/tests/code/csdlreader.cs new file mode 100644 index 0000000..c88333e --- /dev/null +++ b/tests/code/csdlreader.cs @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/// <summary> +/// Class used to parse csdl to create the metatdata object +/// </summary> +/// + +namespace DataJS.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml.Linq; + + + public static class CsdlReader + { + static readonly string knownNamespace = "http://docs.oasis-open.org"; + static readonly string[] repeatingElements = + { + "Action", + "ActionImport", + "Annotation", + "Annotations", + "Apply", + "Binary", + "Bool", + "Cast", + "Collection", + "ComplexType", + "Date", + "DateTimeOffset", + "Decimal", + "Duration", + "EntitySet", + "EntityType", + "EnumMember", + "EnumType", + "Float", + "Function", + "FunctionImport", + "Guid", + "If", + "Int", + "IsOf", + "Key", + "LabeledElement", + "LabeledElementReference", + "Member", + "NavigationProperty", + "NavigationPropertyBinding", + "NavigationPropertyPath", + "Null", + "OnDelete", + "Path", + "Parameter", + "Property", + "PropertyPath", + "PropertyRef", + "PropertyValue", + "Record", + "ReferentialConstraint", + "String", + "Schema", + "Singleton", + "Term", + "TimeOfDay", + "TypeDefinition", + "UrlRef", + "Reference", + "Include", + "IncludeAnnotations" + }; + + public static Dictionary<string, object> ReadCsdl(TextReader payload) + { + return BuildElementJsonObject(XElement.Load(payload)); + } + + /// <summary> + /// Build the attribute object + /// </summary> + /// <param name="xmlAttributes">IEnumberable of XAttributes to build the attribute object</param> + /// <returns>The JsonObject containing the name-value pairs for an element's attributes</returns> + static Dictionary<string, object> BuildAttributeJsonObject(IEnumerable<XAttribute> xmlAttributes) + { + Dictionary<string, object> jsonObject = new Dictionary<string, object>(); + + foreach (XAttribute attribute in xmlAttributes) + { + if (!attribute.IsNamespaceDeclaration) + { + string attributeNamespace = attribute.Name.Namespace.ToString(); + if (string.IsNullOrEmpty(attributeNamespace) || + attributeNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase)) + { + jsonObject[MakeFirstLetterLowercase(attribute.Name.LocalName)] = attribute.Value; + } + } + } + + return jsonObject; + } + + /// <summary> + /// Creates a JsonObject from an XML container element with each attribute or subelement as a property + /// </summary> + /// <param name="container">The XML container</param> + /// <param name="buildValue">Function that builds a value from a property element</param> + /// <returns>The JsonObject containing the name-value pairs</returns> + public static Dictionary<string, object> BuildElementJsonObject(XElement container) + { + if (container == null) + { + return null; + } + + Dictionary<string, object> jsonObject = new Dictionary<string, object>(); + string keyName = MakeFirstLetterLowercase(container.Name.LocalName); + + if (container.HasAttributes || container.HasElements) + { + Dictionary<string, List<Dictionary<string, object>>> repeatingObjectArrays = new Dictionary<string, List<Dictionary<string, object>>>(); + + jsonObject = BuildAttributeJsonObject(container.Attributes()); + + foreach (XElement propertyElement in container.Elements()) + { + string propertyName = MakeFirstLetterLowercase(propertyElement.Name.LocalName); + string properyNamespace = propertyElement.Name.Namespace.ToString(); + + if (string.IsNullOrEmpty(properyNamespace) || properyNamespace.StartsWith(knownNamespace, StringComparison.InvariantCultureIgnoreCase)) + { + // Check to see if the element is repeating and needs to be an array + if (repeatingElements.Contains(propertyElement.Name.LocalName)) + { + // See if property was already created as an array, if not then create it + if (!repeatingObjectArrays.ContainsKey(propertyName)) + { + repeatingObjectArrays.Add(propertyName, new List<Dictionary<string, object>>()); + } + + repeatingObjectArrays[propertyName].Add(BuildElementJsonObject(propertyElement)); + } + else + { + jsonObject[propertyName] = BuildElementJsonObject(propertyElement); + } + } + } + + foreach (string key in repeatingObjectArrays.Keys) + { + jsonObject[key] = repeatingObjectArrays[key].ToArray(); + } + } + else + { + jsonObject[MakeFirstLetterLowercase(container.Name.LocalName)] = container.Value; + } + + return jsonObject; + } + + /// <summary> + /// Makes the first letter of a string lowercase + /// </summary> + /// <param name="name">The string to be modified</param> + /// <returns>Modified string</returns> + private static string MakeFirstLetterLowercase(string str) + { + if (!string.IsNullOrWhiteSpace(str)) + { + if (str.Length > 1 && !(str[1].ToString() == str[1].ToString().ToUpper())) + { + return str[0].ToString().ToLower() + str.Substring(1); + } + else + { + return str; + } + } + + return str; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/code/jsdate.cs ---------------------------------------------------------------------- diff --git a/tests/code/jsdate.cs b/tests/code/jsdate.cs new file mode 100644 index 0000000..4439a3a --- /dev/null +++ b/tests/code/jsdate.cs @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/// <summary> +/// The verifiers's representation of a Javascript date object as deserialized by the library +/// </summary> + +namespace DataJS.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Runtime.Serialization; + using System.ServiceModel; + using System.ServiceModel.Activation; + using System.ServiceModel.Syndication; + using System.ServiceModel.Web; + using System.Xml; + using System.Xml.Linq; + using Microsoft.Spatial; + using Microsoft.OData.Core; + + [Serializable] + public class JsDate : JsonObject + { + private static readonly DateTime JsEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public JsDate(DateTime dateTime) + : base() + { + this["milliseconds"] = dateTime.Subtract(JsEpoch).TotalMilliseconds; + } + + public JsDate(DateTimeOffset dateTimeOffset) + : this(dateTimeOffset.UtcDateTime) + { + this["__edmType"] = "Edm.DateTimeOffset"; + this["__offset"] = (dateTimeOffset.Offset < TimeSpan.Zero ? "-" : "+") + dateTimeOffset.Offset.ToString("hh':'mm"); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/code/jsonobject.cs ---------------------------------------------------------------------- diff --git a/tests/code/jsonobject.cs b/tests/code/jsonobject.cs new file mode 100644 index 0000000..27f4b9b --- /dev/null +++ b/tests/code/jsonobject.cs @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/// <summary> +/// A weakly typed representation of a JSON object using a dictionary implementation +/// </summary> +/// <typeparam name="T">The CLR type of the values of the properties</typeparam> + +namespace DataJS.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.Serialization; + + [Serializable] + [KnownType(typeof(JsonObject))] + [KnownType(typeof(JsonObject[]))] + [KnownType(typeof(JsDate))] + [KnownType(typeof(List<object>))] + public class JsonObject : ISerializable, IEnumerable<KeyValuePair<string, object>> + { + Dictionary<string, object> dictionary = new Dictionary<string, object>(); + + public void Remove(string key) + { + dictionary.Remove(key); + } + + public object this[string key] + { + get + { + return this.dictionary[key]; + } + set + { + this.dictionary[key] = value; + } + } + + public bool ContainsKey(string key) + { + return this.dictionary.ContainsKey(key); + } + + public static JsonObject Merge(JsonObject first, JsonObject second) + { + if (first == null) + { + return second; + } + + if (second != null) + { + JsonObject merged = new JsonObject(); + merged.dictionary = new Dictionary<string, object>(first.dictionary); + foreach (var pair in second.dictionary) + { + merged.dictionary[pair.Key] = pair.Value; + } + return merged; + } + return first; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + this.dictionary.ToList().ForEach(pair => info.AddValue(pair.Key, pair.Value)); + } + + public IEnumerator<KeyValuePair<string, object>> GetEnumerator() + { + return this.dictionary.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.dictionary.GetEnumerator(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/code/readerutils.cs ---------------------------------------------------------------------- diff --git a/tests/code/readerutils.cs b/tests/code/readerutils.cs new file mode 100644 index 0000000..094a70c --- /dev/null +++ b/tests/code/readerutils.cs @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Web.Script.Serialization; + +namespace DataJS.Tests +{ + public static class ReaderUtils + { + public static JsonObject CreateEntryPropertyMetadata(string type) + { + return CreateEntryPropertyMetadata(type, true); + } + + public static JsonObject CreateEntryPropertyMetadata(string type, bool withExtensions) + { + JsonObject json = new JsonObject(); + json["type"] = type; + + + // TODO: add proper support for property extensions + if (withExtensions) + { + json["extensions"] = new JsonObject[] { }; + } + + return json; + } + + public static JsonObject CreateExtension(string name, string nameSpace, string value) + { + JsonObject json = new JsonObject(); + json["name"] = name; + json["namespaceURI"] = nameSpace; + json["value"] = value; + return json; + } + + public static WebRequest CreateRequest(string url, string user = null, string password = null) + { + WebRequest request = WebRequest.Create(url); + if (user != null || password != null) + { + request.Credentials = new NetworkCredential(user, password); + request.PreAuthenticate = true; + } + + return request; + } + + public static Stream ConvertDictionarytoJsonlightStream(Dictionary<string, object> dict) + { + MemoryStream stream = new MemoryStream(); + if (dict == null) + { + return stream; + } + + string jsonString = new JavaScriptSerializer().Serialize(dict); + StreamWriter writer = new StreamWriter(stream); + writer.Write(jsonString); + writer.Flush(); + stream.Position = 0; + return stream; + } + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/CacheVerifier.js ---------------------------------------------------------------------- diff --git a/tests/common/CacheVerifier.js b/tests/common/CacheVerifier.js new file mode 100644 index 0000000..1e9f0d4 --- /dev/null +++ b/tests/common/CacheVerifier.js @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// CacheVerifier.js +// This object verifies the operation of the cache. +// Internally it maintains a simple model of the cache implemented using a lookup array of the expected cached pages. + +(function (window, undefined) { + + var CacheVerifier = function (baseUri, pageSize, total, cacheSize) { + /** Creates a new CacheVerifier + * @param {String} baseUri - The base URI of the collection + * @param {Integer} pageSize - The page size used in the cache + * @param {Integer} total - The total number of items in the collection + * @param {Integer} cacheSize - Cache size in bytes + */ + this.baseUri = baseUri; + this.pageSize = pageSize; + this.total = total; + this.cacheSize = (cacheSize !== undefined) ? cacheSize : 1024 * 1024; + this.actualSize = 0; + this.actualCount = 0; + this.cachedPages = []; + this.exactPageCount = (total % pageSize === 0); + this.maxPage = Math.floor(total / pageSize); + this.overflowed = this.cacheSize === 0; + }; + + CacheVerifier.mechanisms = { + memory: "memory", + indexeddb: "indexeddb", + dom: "dom", + best: "best" + }; + + CacheVerifier.isMechanismAvailable = function (mechanism) { + /** Determines if the specified local storage mechanism is available + * @param mechanism - The name of the mechanism + * @returns Whether the mechanism is available + */ + switch (mechanism) { + case CacheVerifier.mechanisms.indexeddb: + if (window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.indexedDB) { + return true; + } + else { + return false; + } + break; + case CacheVerifier.mechanisms.dom: + if (window.localStorage) { + return true; + } + else { + return false; + } + break; + case CacheVerifier.mechanisms.memory: + case CacheVerifier.mechanisms.best: + case undefined: + return true; + default: + return false; + } + } + + CacheVerifier.prototype.clear = function () { + /** Clears the cache in the verifier + */ + this.cachedPages = []; + this.actualSize = 0; + this.actualCount = 0; + this.overflowed = this.cacheSize === 0; + } + + CacheVerifier.prototype.verifyRequests = function (requests, responses, index, count, description, backwards, isPrefetch) { + /** Verifies the HTTP requests for a single data request, and updates the verifier with cached pages + * @param {Array} requests - The sequence of request objects (from OData.defaultHttpClient) + * @param {Array} responses - The sequence of response objects (from OData.defaultHttpClient) + * @param {Integer} index - The starting index of the read + * @param {Integer} count - The count of items in the read + * @param {String} description - The description of the requests being verified + * @param {Boolean} backwards - Whether or not filterBack is being verified + * @param {Boolean} isPrefetch - Whether the requests being verified come from the prefetcher + */ + var that = this; + + index = (index < 0 ? 0 : index); + var pageIndex = function (index) { + /** Returns the page index that the given item index belongs to + * @param {Integer} index - The item index + * @returns The page index + */ + return Math.floor(index / that.pageSize); + }; + + var estimateSize = function (obj) { + /** Estimates the size of an object in bytes. + * @param {Object} obj - Object to determine the size of. + * @returns {Number} Estimated size of the object in bytes. + */ + + var size = 0; + var type = typeof obj; + + if (type === "object" && obj) { + for (var name in obj) { + size += name.length * 2 + estimateSize(obj[name]); + } + } else if (type === "string") { + size = obj.length * 2; + } else { + size = 8; + } + return size; + }; + + var expectedUris = []; + var responseIndex = 0; + if (count >= 0) { + var minPage = pageIndex(index); + var maxPage = Math.min(pageIndex(index + count - 1), pageIndex(this.total)); + + // In the case that the index is outside the range of the collection the minPage will be greater than the maxPage + maxPage = Math.max(minPage, maxPage); + + if (!(isPrefetch && !this.exactPageCount && minPage > this.maxPage)) { + for (var page = minPage; page <= maxPage && this.actualCount <= this.total && !(isPrefetch && this.overflowed); page++) { + if (!this.cachedPages[page]) { + + expectedUris.push(that.baseUri + "?$skip=" + page * this.pageSize + "&$top=" + (this.pageSize)); + + var actualPageSize = 0; + var actualPageCount = 0; + if (responses[responseIndex] && responses[responseIndex].data) { + actualPageSize += estimateSize(responses[responseIndex].data); + actualPageCount += responses[responseIndex].data.value.length; + // Handle server paging skipToken requests + while (responses[responseIndex].data["@odata.nextLink"]) { + var nextLink = responses[responseIndex].data["@odata.nextLink"]; + if (nextLink) { + var index = that.baseUri.indexOf(".svc/", 0); + if (index != -1) { + nextLink = that.baseUri.substring(0, index + 5) + nextLink; + } + } + + expectedUris.push(nextLink); + responseIndex++; + actualPageSize += estimateSize(responses[responseIndex].data); + actualPageCount += responses[responseIndex].data.value.length; + } + + actualPageSize += 24; // 24 byte overhead for the pages (i)ndex, and (c)ount fields + } + + responseIndex++; + + this.overflowed = this.cacheSize >= 0 && this.actualSize + actualPageSize > this.cacheSize; + if (!this.overflowed) { + this.cachedPages[page] = true; + this.actualSize += actualPageSize; + this.actualCount += actualPageCount; + } + } + } + } + } + + if (backwards) { + expectedUris.reverse(); + } + + var actualUris = $.map(requests, function (r) { return r.requestUri; }); + djstest.assertAreEqualDeep(actualUris, expectedUris, description); + }; + + CacheVerifier.getExpectedFilterResults = function (data, filterIndex, filterCount, predicate, backwards) { + /** Verifies the cache filter returns the correct data + * @param {Array} collection - Array of items in the collection + * @param {Integer} filterIndex - The index value + * @param {Integer} filterCount - The count value + * @param {Function} predicate - Predicate to be applied in filter, takes an item + * @param {Boolean} backwards - Whether or not filterBackwards is being verified + */ + if (!data || !data.value) { + return data; + } + + var value = []; + if (filterCount !== 0) { + // Convert [item0, item1, ...] into [{ index: 0, item: item0 }, { index: 1, item: item1 }, ...] + var indexedCollection = $.map(data.value, function (item, index) { + return { index: index, item: item }; + }); + + var grepPredicate = function (element, index) { + return predicate(element.item); + }; + + var index = filterIndex < 0 ? 0 : filterIndex; + var count = filterCount < 0 ? indexedCollection.length : filterCount; + + value = backwards ? + // Slice up to 'index', filter, then slice 'count' number of items from the end + $.grep(indexedCollection.slice(0, index + 1), grepPredicate).slice(-count) : + // Slice from 'index' to the end, filter, then slice 'count' number of items from the beginning + $.grep(indexedCollection.slice(index), grepPredicate).slice(0, count); + } + + var expectedResults = {}; + for (var property in data) { + if (property == "value") { + expectedResults[property] = value; + } else { + expectedResults[property] = data[property]; + } + } + + return expectedResults; + }; + + window.CacheVerifier = CacheVerifier; + +})(this); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/Instrument.js ---------------------------------------------------------------------- diff --git a/tests/common/Instrument.js b/tests/common/Instrument.js new file mode 100644 index 0000000..fab583a --- /dev/null +++ b/tests/common/Instrument.js @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Instrument.js +// Instrumentation utilities + +(function (window, undefined) { + + var warmedUp = false; + var getBrowserMemorySize = function (success) { + /** Gets the memory size (in bytes) of the browser process + * @param {Function} success - The success callback + */ + var makeRequest = function (success) { + $.get("./common/Instrument.svc/GetBrowserMemorySize", function (data) { + success(parseInt(data)); + }, "text"); + }; + + if (window.CollectGarbage) { + window.CollectGarbage(); + } + + if (!warmedUp) { + // Make a dummy request to warm it up + makeRequest(function () { + warmedUp = true; + makeRequest(success); + }); + } else { + makeRequest(success); + } + } + + window.Instrument = { + getBrowserMemorySize: getBrowserMemorySize + }; + +})(this); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/Instrument.svc ---------------------------------------------------------------------- diff --git a/tests/common/Instrument.svc b/tests/common/Instrument.svc new file mode 100644 index 0000000..111020f --- /dev/null +++ b/tests/common/Instrument.svc @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. +*/ + +<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" + Service="DataJS.Tests.Instrument" %> + +namespace DataJS.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Runtime.Serialization; + using System.ServiceModel; + using System.ServiceModel.Activation; + using System.ServiceModel.Syndication; + using System.ServiceModel.Web; + using System.Text; + + /// <summary> + /// Instrumentation utilities + /// </summary> + [ServiceContract] + [ServiceBehavior(IncludeExceptionDetailInFaults = true)] + [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] + public class Instrument + { + static readonly Dictionary<string, string> userAgents = new Dictionary<string,string> + { + { "MSIE", "iexplore" }, + { "Firefox", "firefox" }, + { "Chrome", "chrome" }, + { "Safari", "safari" } + }; + + /// <summary> + /// Gets the memory size used by the browser + /// </summary> + /// <returns>The memory size used by the browser (in bytes), or zero if browser is not supported</returns> + [OperationContract] + [WebGet] + public Stream GetBrowserMemorySize() + { + string userAgentString = WebOperationContext.Current.IncomingRequest.UserAgent; + string userAgentKey = Instrument.userAgents.Keys.FirstOrDefault(ua => userAgentString.Contains(ua)); + + if (userAgentKey != null) + { + string processName = userAgents[userAgentKey]; + long totalMemory = Process.GetProcessesByName(processName).Select(p => p.WorkingSet64).Sum(); + + return new MemoryStream(Encoding.UTF8.GetBytes(totalMemory.ToString())); + } + else + { + return new MemoryStream(Encoding.UTF8.GetBytes("0")); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/ODataVerifyReader.svc ---------------------------------------------------------------------- diff --git a/tests/common/ODataVerifyReader.svc b/tests/common/ODataVerifyReader.svc new file mode 100644 index 0000000..6b71acf --- /dev/null +++ b/tests/common/ODataVerifyReader.svc @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. +*/ + +<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" + Service="DataJS.Tests.ODataVerifyReader" %> + +//uncomment this line to debug JSON serialization. +//#define DEBUG_SERIALIZATION + +namespace DataJS.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Runtime.Serialization; + using System.ServiceModel; + using System.ServiceModel.Activation; + using System.ServiceModel.Syndication; + using System.ServiceModel.Web; + using System.Xml; + using System.Xml.Linq; + using Microsoft.Spatial; + using Microsoft.OData.Core; + using System.Web.Script.Serialization; + + /// <summary> + /// Verifier for the OData.read library function + /// </summary> + [ServiceContract] + [ServiceBehavior(IncludeExceptionDetailInFaults = true)] + [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] + public class ODataVerifyReader + { + const string jsonlightMediaType = "application/json"; + + /// <summary> + /// Reads a URI that will return a metadata object + /// </summary> + /// <param name="url">The URL to send the request to</param> + /// <returns>Stream of metadata in json light format</returns> + [OperationContract] + [WebGet] + public Stream ReadMetadata(string url) + { + WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse(); + Dictionary<string, object> jsonObject = CsdlReader.ReadCsdl(new StreamReader(response.GetResponseStream())); + return ReaderUtils.ConvertDictionarytoJsonlightStream(jsonObject); + } + + /// <summary> + /// Reads a URI that will get the Json response and return the stream + /// </summary> + /// <param name="url">URL of the entry</param> + /// <param name="user">The username for basic authentication</param> + /// <param name="password">The password for basic authentication</param> + /// <returns>Stream of the Json response expected to be returned by OData.read</returns> + [OperationContract] + [WebGet(ResponseFormat = WebMessageFormat.Json)] + public Stream ReadJson(string url, string mimeType, string user, string password) + { + if (mimeType == null) + { + mimeType = jsonlightMediaType + ";odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8"; + } + + HttpWebRequest request = (HttpWebRequest)ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password); + request.Accept = mimeType; + WebResponse response = request.GetResponse(); + + return response.GetResponseStream(); + } + + /// <summary> + /// Resolves the given url string to a URI + /// </summary> + /// <param name="url">The given URL string</param> + /// <param name="urlKind">URI kind to resolve to</param> + /// <returns>The resolved URI</returns> + private static string ResolveUri(string url, UriKind uriKind) + { + Uri resolvedUri = new Uri(url, UriKind.RelativeOrAbsolute); + if (!resolvedUri.IsAbsoluteUri) + { + // If the given URI is relative, then base it on the Referer URI + Uri baseUri = new Uri(WebOperationContext.Current.IncomingRequest.Headers["Referer"]); + resolvedUri = new Uri(baseUri, resolvedUri); + if (uriKind == UriKind.Relative) + { + resolvedUri = baseUri.MakeRelativeUri(resolvedUri); + } + } + + return resolvedUri.ToString(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/503b4417/tests/common/ObservableHttpClient.js ---------------------------------------------------------------------- diff --git a/tests/common/ObservableHttpClient.js b/tests/common/ObservableHttpClient.js new file mode 100644 index 0000000..9be75f8 --- /dev/null +++ b/tests/common/ObservableHttpClient.js @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// ObservableHttpClient.js +// This object extends OData's default httpClient by supporting request and response recording sessions, and firing a custom +// JQuery event for each request/response. +// +// The events fired by this object are: +// request: Before a request is made +// success: Before the primary success handler is called +// +// To bind to an event, JQuery event attachers can be used on the object, e.g. +// $(observableHttpClient).bind("request", function (request) { ... }); +// +// To begin a new recording session, use: +// var session = observableHttpClient.newSession(); +// +// Requests and responses are then recorded in session.requests and session.responses. Session can be ended by session.end(). +// Multiple simultaneous sessions are supported. + +(function (window, undefined) { + + var ObservableHttpClient = function (provider) { + this.provider = provider ? provider : window.odatajs.oData.net.defaultHttpClient; + }; + + ObservableHttpClient.prototype.newSession = function () { + return new Session(this); + }; + + ObservableHttpClient.prototype.request = function (request, success, error) { + var that = this; + + $(this).triggerHandler("request", request); + return this.provider.request(request, function (response) { + $(that).triggerHandler("success", response); + success(response); + }, error); + }; + + + var Session = function (client) { + var that = this; + + this.client = client; + this.clear(); + + this.requestHandler = function (event, request) { that.requests.push(request); }; + $(client).bind("request", this.requestHandler); + + this.successHandler = function (event, response) { that.responses.push(response); }; + $(client).bind("success", this.successHandler); + }; + + Session.prototype.clear = function () { + this.requests = []; + this.responses = []; + } + + Session.prototype.end = function () { + $(this.client).unbind("request", this.requestHandler); + $(this.client).unbind("success", this.successHandler); + }; + + window.ObservableHttpClient = ObservableHttpClient; + window.Session = Session; + +})(this); \ No newline at end of file
