Example #1
0
        /// <summary>
        /// Gets the scope name from the query string.
        /// </summary>
        /// <returns>scope name</returns>
        private string GetScopeName(string[] RelativeUriSegments)
        {
            // The item at index 0 of the RelativeUriSegments array contains the scope name.
            if (/*_serviceHost.*/ RelativeUriSegments.Length < 1 || String.IsNullOrEmpty(/*_serviceHost.*/ RelativeUriSegments[0].Trim()))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.MissingScopeNameInRequest);
            }

            // Scope names are compared in a case insensitive manner and used as keys in lower case.
            return /*_serviceHost.*/ (RelativeUriSegments[0].ToLowerInvariant());
        }
Example #2
0
        /// <summary>
        /// Generate the Id for an entity. The format currently is the OData Id.
        /// i.e. http://baseUri/tableName(primarykeylist)
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        internal static string GenerateOfflineEntityId(IOfflineEntity entity)
        {
            var primaryKeyString = ReflectionUtility.GetPrimaryKeyString(entity);

            if (String.IsNullOrEmpty(primaryKeyString))
            {
                throw SyncServiceException.CreateInternalServerError(
                          String.Format("GetPrimaryKeyString method returned an empty string for entity type {0}.", entity.GetType()));
            }

            return(String.Format(@"{0}/{1}({2})", "http://bitmobile.com/" + AppDomain.CurrentDomain.FriendlyName /*WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri*/, entity.GetType().Name, primaryKeyString));
        }
Example #3
0
        internal static SyncConflictResolution GetSyncConflictResolution(ConflictResolutionPolicy conflictResolutionPolicy)
        {
            switch (conflictResolutionPolicy)
            {
            case ConflictResolutionPolicy.ClientWins:
                return(SyncConflictResolution.ClientWins);

            case ConflictResolutionPolicy.ServerWins:
                return(SyncConflictResolution.ServerWins);

            default:
                throw SyncServiceException.CreateInternalServerError(Strings.UnsupportedConflictResolutionPolicy);
            }
        }
Example #4
0
        internal static string GetContentType(SyncSerializationFormat format)
        {
            switch (format)
            {
            case SyncSerializationFormat.ODataAtom:
                return("application/atom+xml");

            case SyncSerializationFormat.ODataJson:
                return("application/json");

            default:
                throw SyncServiceException.CreateBadRequestError("Unsupported serialization format");
            }
        }
Example #5
0
        /// <summary>
        /// Verify query parameters for '$' etc.
        /// </summary>
        internal void VerifyQueryParameters()
        {
            NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters;

            var queryDictionary = new Dictionary <string, string>();

            foreach (string key in values.Keys)
            {
                if (!queryDictionary.ContainsKey(key))
                {
                    queryDictionary.Add(key, values[key]);
                }
                else
                {
                    throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
                }
            }
        }
Example #6
0
        private void ProcessSyncServiceException(SyncServiceException syncServiceException)
        {
            string exceptionMessage = WebUtil.GetExceptionMessage(syncServiceException);

            SyncServiceTracer.TraceWarning(exceptionMessage);

            _outgoingMessage = _syncConfiguration.UseVerboseErrors
                                   ? CreateExceptionMessage((HttpStatusCode)syncServiceException.StatusCode, exceptionMessage)
                                   : CreateExceptionMessage((HttpStatusCode)syncServiceException.StatusCode, syncServiceException.Message);

            // Add the "Allow" HTTP header if present._outgoingMessage.Properties[HttpResponseMessageProperty.Name].
            if (!String.IsNullOrEmpty(syncServiceException.ResponseAllowHeader) &&
                null != _outgoingMessage.Properties[HttpResponseMessageProperty.Name])
            {
                ((HttpResponseMessageProperty)_outgoingMessage.Properties[HttpResponseMessageProperty.Name]).
                Headers.Add("Allow", syncServiceException.ResponseAllowHeader);
            }
        }
Example #7
0
        /// <summary>
        /// Get a dictionary that contains command parameters.
        /// </summary>
        /// <param name="queryStringCollection">Querystring represented as a NameValueCollection</param>
        /// <returns>Dictionary of command parameter types and values.</returns>
        private Dictionary <CommandParamType, object> GetCommandParameters(NameValueCollection queryStringCollection)
        {
            var commandParams = new Dictionary <CommandParamType, object>();

            Dictionary <string, string> filterParams = GetFilterParamsFromIncomingRequest(queryStringCollection);

            commandParams.Add(CommandParamType.FilterParameters, filterParams);

            string scopeName = GetScopeName();

            // Check the scope requested against the list of enabled scopes.
            if (!_configuration.ScopeNames.Contains(scopeName.ToLowerInvariant()))
            {
                throw SyncServiceException.CreateBadRequestError(Strings.SyncScopeNotSupported);
            }

            commandParams.Add(CommandParamType.ScopeName, scopeName);

            return(commandParams);
        }
        /// <summary>
        /// Invokes the InitializeService user method.
        /// </summary>
        /// <param name="type">service type (used for reflection)</param>
        private void InvokeStaticInitialization(Type type)
        {
            // Search for the InitializeService method going from most-specific to least-specific type.

            const BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly;

            while (type != null)
            {
                MethodInfo info = type.GetMethod("InitializeService", bindingAttr, null, new[] { typeof(ISyncServiceConfiguration) }, null);

                if ((info != null) && (info.ReturnType == typeof(void)))
                {
                    ParameterInfo[] parameters = info.GetParameters();

                    if ((parameters.Length == 1) && !parameters[0].IsOut)
                    {
                        var objArray = new object[] { this };

                        try
                        {
                            info.Invoke(null, objArray);

                            return;
                        }
                        catch (TargetInvocationException exception)
                        {
                            SyncTracer.Warning("Exception invoking the static InitializeService method. Details {0}", WebUtil.GetExceptionMessage(exception));

                            ErrorHandler.HandleTargetInvocationException(exception);

                            throw;
                        }
                    }
                }

                type = type.BaseType;
            }

            // We should never exit from here when the InitializeService method is implemented.
            throw SyncServiceException.CreateInternalServerError(Strings.InitializeServiceMethodNotImplemented);
        }
Example #9
0
        /// <summary>
        /// Get the value for a query string item.
        /// </summary>
        /// <param name="item">Item to search for in the incoming request uri.</param>
        /// <returns>Value of the item.</returns>
        internal string GetQueryStringItem(string item)
        {
            NameValueCollection values = _operationContext.IncomingRequest.UriTemplateMatch.QueryParameters;

            // Check if we have the item by using a direct function (good performance)
            // Note: the underlying data structure in a NameValueCollection is a hashtable.
            string[] strArray = values.GetValues(item);

            // If no match found, then loop through all keys and do a case insensitive search.
            if ((strArray == null) || (strArray.Length == 0))
            {
                string str = null;
                foreach (string str2 in values.Keys)
                {
                    if ((str2 != null) && StringComparer.OrdinalIgnoreCase.Equals(str2.Trim(), item))
                    {
                        if (str != null)
                        {
                            // Bad request
                            throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
                        }
                        str      = str2;
                        strArray = values.GetValues(str2);
                    }
                }
                if ((strArray == null) || (strArray.Length == 0))
                {
                    return(null);
                }
            }

            if (strArray.Length != 1)
            {
                // syntax error, cannot have multiple querystring parameters with the same name.
                throw SyncServiceException.CreateBadRequestError(Strings.DuplicateParametersInRequestUri);
            }

            return(strArray[0]);
        }
Example #10
0
        /// <summary>
        /// Validate the HTTP Verb for GET/POST and check if the URL matches the allowed format.
        /// </summary>
        internal void ValidateRequestHttpVerbAndSegments()
        {
            if (_operationContext == null)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.NullWebOperationContext);
            }

            // Only allow GET and POST verbs
            if (0 != String.Compare(RequestHttpMethod, "GET", StringComparison.InvariantCultureIgnoreCase) &&
                0 != String.Compare(RequestHttpMethod, "POST", StringComparison.InvariantCultureIgnoreCase))
            {
                SyncTracer.Warning("Request HTTP method is not GET or POST. HTTP Method: {0}", RequestHttpMethod ?? String.Empty);
                throw SyncServiceException.CreateMethodNotAllowed(Strings.UnsupportedHttpMethod, "GET, POST");
            }

            // Check if we have less than 2 relative segments. The maximum segments we can have is 2
            // which is for /scope/operation.
            if (RelativeUriSegments.Length > 2)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.InvalidUrlFormat);
            }
        }
 /// <summary>
 /// Add a new filter parameter configuration.
 /// </summary>
 /// <param name="queryStringParam">Name of the querystring parameter</param>
 /// <param name="tableName">SQL table name</param>
 /// <param name="sqlParameterName">SQL parameter name (has to be exact since its used in query formation)</param>
 /// <param name="typeOfParam">Indicates the Type of the parameter</param>
 public void AddFilterParameterConfiguration(string queryStringParam, string tableName, string sqlParameterName, Type typeOfParam)
 {
     // Check if there is a filter parameter which has the same querystring, tablename and sqlparameter name.
     if (0 == _filterParameters.Where(p =>
                                      0 == String.Compare(p.QueryStringKey, queryStringParam, StringComparison.InvariantCultureIgnoreCase) &&
                                      0 == String.Compare(p.TableName, tableName, StringComparison.InvariantCultureIgnoreCase) &&
                                      0 == String.Compare(p.SqlParameterName, sqlParameterName, StringComparison.InvariantCultureIgnoreCase)
                                      ).Count())
     {
         _filterParameters.Add(new SqlSyncProviderFilterParameterInfo
         {
             QueryStringKey   = queryStringParam.ToLowerInvariant(),
             SqlParameterName = sqlParameterName,
             TableName        = tableName,
             ValueType        = typeOfParam
         });
     }
     else
     {
         throw SyncServiceException.CreateInternalServerError(Strings.DuplicateFilterParameter);
     }
 }
Example #12
0
        internal Request(RequestCommand requestCommand,
                         HttpContextServiceHost serviceHost,
                         Dictionary <CommandParamType, object> commandParams,
                         byte[] blob,
                         List <IOfflineEntity> entities,
                         SyncSerializationFormat responseSerializationFormat)
        {
            IdToTempIdMapping = new Dictionary <string, string>();

            RequestCommand = requestCommand;
            ServiceHost    = serviceHost;
            CommandParams  = commandParams;
            SyncBlob       = blob;
            ResponseSerializationFormat = responseSerializationFormat;

            if (null != entities && requestCommand != RequestCommand.UploadChanges)
            {
                throw SyncServiceException.CreateBadRequestError(Strings.EntitiesOnlyAllowedForUploadChangesRequest);
            }

            EntityList = entities;
        }
Example #13
0
        /// <summary>Processes the diagnostics page request.</summary>
        /// <returns>The response <see cref="Message"/>.</returns>
        public Message ProcessRequestForDiagnostics()
        {
            try
            {
                // Intialize the service host.
                _serviceHost = new HttpContextServiceHost();

                // Ensure configuration.
                CreateConfiguration();

                Debug.Assert(_syncConfiguration != null, "_syncConfiguration != null");

                // Check the EnableDiagPage property and invoke the callback if necessary.
                // Return 404-Not Found if not enabled.
                if (!_syncConfiguration.EnableDiagnosticPage || !OnBeginDiagnosticRequest())
                {
                    throw SyncServiceException.CreateResourceNotFound();
                }

                // Perform diagnostic checks and prepare outgoing message.
                _outgoingMessage = DiagHelper.CreateDiagResponseMessage(_syncConfiguration, _serviceHost);
            }
            catch (SyncServiceException syncServiceException)
            {
                ProcessSyncServiceException(syncServiceException);
            }
            catch (Exception exception)
            {
                if (WebUtil.IsFatalException(exception))
                {
                    throw;
                }

                _outgoingMessage = CreateMessageFromUnhandledException(exception);
            }

            return(_outgoingMessage);
        }
        /// <summary>
        /// Invokes the static InitializeService method that allows one-time service wide policy configuration.
        /// </summary>
        /// <param name="syncServiceType">service type (used for reflection).</param>
        internal void Initialize(Type syncServiceType)
        {
            if (!IsInitialized)
            {
                lock (_lockObject)
                {
                    if (!IsInitialized)
                    {
                        // Initialize the filter parameter list, to remove data from previously
                        // failed initialization attempt.
                        _filterParameters = new List <SqlSyncProviderFilterParameterInfo>();

                        // Invoke the static InitializeService method.
                        InvokeStaticInitialization(syncServiceType);

                        // Build Sync Operation Inspectors list
                        ReadSyncInterceptors(syncServiceType);

                        // Check for empty connection string.
                        if (String.IsNullOrEmpty(ServerConnectionString))
                        {
                            throw SyncServiceException.CreateInternalServerError(Strings.ConnectionStringNotSet);
                        }

                        // There are no dynamic scope changes allowed anyway, so why should the service start.
                        if (0 == ScopeNames.Count)
                        {
                            throw SyncServiceException.CreateInternalServerError(Strings.NoScopesVisible);
                        }

                        // We are initialized now.
                        IsInitialized = true;
                    }
                }
            }
        }
Example #15
0
 /// <summary>Creates a new "Method Not Allowed" exception.</summary>
 /// <param name="errorMessage">Plain text error message for this exception.</param>
 /// <param name="allow">String value for 'Allow' header in response.</param>
 /// <returns>A new SyncServiceException to indicate the requested method is not allowed on the response.</returns>
 public static SyncServiceException CreateMethodNotAllowed(string errorMessage, string allow)
 {
     var exception = new SyncServiceException(ResponseHttpStatusCode.MethodNotAllowed, errorMessage) { _responseAllowHeader = allow };
     return exception;
 }
Example #16
0
        /// <summary>
        /// Read and parse the incoming request stream for a POST request.
        /// </summary>
        private void ReadIncomingRequestStreamForPost()
        {
            if (null == _serviceHost.RequestStream || !_serviceHost.RequestStream.CanRead)
            {
                SyncTracer.Info("Request stream for HTTP POST is empty, null or cannot be read.");
                return;
            }

            try
            {
                var reader = WebUtil.GetSyncReader(_serviceHost.GetRequestContentSerializationFormat(),
                                                   _serviceHost.RequestStream,
                                                   _configuration.TypeToTableGlobalNameMapping.Keys.ToArray());

                reader.Start();

                while (reader.Next())
                {
                    switch (reader.ItemType)
                    {
                    case ReaderItemType.Entry:
                        IOfflineEntity entity = reader.GetItem();

                        if (entity.ServiceMetadata.IsTombstone)
                        {
                            if (String.IsNullOrEmpty(entity.ServiceMetadata.Id))
                            {
                                throw SyncServiceException.CreateBadRequestError(Strings.TombstoneEntityHasNoId);
                            }

                            WebUtil.ParseIdStringAndPopulateKeyFields(entity, _serviceHost.ServiceBaseUri);
                        }

                        _entityList.Add(entity);

                        bool hasTempId = false;
                        if (reader.HasTempId())
                        {
                            // Save the entity id to tempId mapping for use later when writing response.
                            _idToTempIdMapping.Add(WebUtil.GenerateOfflineEntityId(entity), reader.GetTempId());

                            hasTempId = true;
                        }

                        // Make sure, we have atleast one of Id or TempId
                        if (String.IsNullOrEmpty(entity.ServiceMetadata.Id) && !hasTempId)
                        {
                            throw SyncServiceException.CreateBadRequestError(Strings.BothIdAndTempIdAreMissing);
                        }

                        break;

                    case ReaderItemType.SyncBlob:
                        _syncBlob = reader.GetServerBlob();

                        break;
                    }
                }
            }
            catch (XmlException exception)
            {
                SyncTracer.Warning("XmlException: {0}", WebUtil.GetExceptionMessage(exception));

                throw SyncServiceException.CreateBadRequestError(Strings.BadRequestPayload);
            }
        }
        /// <summary>
        /// Reflect the types from T and cache it in a list for reference.
        /// </summary>
        private void DiscoverTypes(Type t)
        {
            TableGlobalNameToTypeMapping = new Dictionary <string, Type>();
            TypeToTableGlobalNameMapping = new Dictionary <Type, string>();
            TypeToTableLocalNameMapping  = new Dictionary <Type, string>();

            // We want to find the underlying type of every generic type which is a private instance member of the templated class
            // and check if it has the [SyncEntityType] attribute applied to it. If this check passes, then
            // extract the type and the attribute information and save it for future reference.
            FieldInfo[] privateTypes = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (var privateType in privateTypes)
            {
                Type fieldType = privateType.FieldType;

                if (fieldType.IsGenericType)
                {
                    Type[] genericArguments = fieldType.GetGenericArguments();

                    if (null != genericArguments && 1 == genericArguments.Length)
                    {
                        var argument = genericArguments[0];

                        var attributes = argument.GetCustomAttributes(false);

                        object syncAttribute = null;
                        foreach (var attribute in attributes)
                        {
                            if (attribute.GetType() == typeof(SyncEntityTypeAttribute))
                            {
                                syncAttribute = attribute;
                                break;
                            }
                        }

                        if (null != syncAttribute)
                        {
                            // Read the TableGlobalName property value
                            PropertyInfo globalTablenamePropertyInfo = syncAttribute.GetType().GetProperty(SyncServiceConstants.SYNC_ENTITY_TYPE_TABLE_GLOBAL_NAME);

                            //Note: We cannot ToLower() this because the datatable name for sqlsyncprovider makes a case sensitive comparison.
                            string globalTableName = Convert.ToString(globalTablenamePropertyInfo.GetValue(syncAttribute, null));

                            if (TableGlobalNameToTypeMapping.ContainsKey(globalTableName))
                            {
                                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, Strings.DuplicateGlobalTableName, globalTableName));
                            }

                            if (String.IsNullOrEmpty(globalTableName))
                            {
                                throw new InvalidOperationException(Strings.TableGlobalNameCannotByEmpty);
                            }

                            TableGlobalNameToTypeMapping.Add(globalTableName, argument.UnderlyingSystemType);

                            TypeToTableGlobalNameMapping.Add(argument.UnderlyingSystemType, globalTableName);

                            // Read the TableLocalName property value
                            PropertyInfo localTablenamePropertyInfo = syncAttribute.GetType().GetProperty(SyncServiceConstants.SYNC_ENTITY_TYPE_TABLE_LOCAL_NAME);

                            string localTableName = Convert.ToString(localTablenamePropertyInfo.GetValue(syncAttribute, null));

                            if (String.IsNullOrEmpty(localTableName))
                            {
                                throw new InvalidOperationException(Strings.TableLocalNameCannotByEmpty);
                            }

                            TypeToTableLocalNameMapping.Add(argument.UnderlyingSystemType, localTableName);
                        }
                    }
                }
            }

            if (0 == TableGlobalNameToTypeMapping.Count)
            {
                throw SyncServiceException.CreateInternalServerError(Strings.NoValidTypeFoundForSync);
            }
        }
Example #18
0
        /// <summary>
        /// Parse the Id string and populate key fields of the object. This is called when the client sends tombstones
        /// and the Key fields are not present in the input payload.
        /// The approach used is to parse each key field individually and set it to a property with the same name.
        /// For example: For http://host/service.svc/Tag(ID=1), we parse out ID=1 and then populate the ID property of the targetObject with the
        /// value 1.
        /// </summary>
        /// <param name="entity">Entity for which we need to set the key fields.</param>
        /// <param name="serviceBaseUri">
        /// Base Uri of the service. The ServiceMetadata.Id property has the Uri which we want to strip off before attempting to
        /// parse the keys and values.
        /// </param>
        internal static void ParseIdStringAndPopulateKeyFields(IOfflineEntity entity, Uri serviceBaseUri)
        {
            Debug.Assert(null != entity);
            Debug.Assert(!String.IsNullOrEmpty(entity.ServiceMetadata.Id));

            string idString = entity.ServiceMetadata.Id;

            // Remove the ServiceUri and the entity type name from the EntityId
            // Note: Case sensitive comparisons are made since the client isn't supposed to change the Id for an
            // entity.
            string serviceUriWithTableName = serviceBaseUri + "/" + entity.GetType().Name;

            // If the Id does not have the correct format of serviceUri/TableName, then we should not continue further.
            if (!idString.StartsWith(serviceUriWithTableName, false, CultureInfo.InvariantCulture))
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, idString));
            }

            // Remove the host and the table name from the Id.
            // Example: http://host/service.svc/table(id=123) will become (id=123)
            idString = idString.Remove(0, serviceUriWithTableName.Length);

            // Remove leading '/' if any. After this the id string is of the format (ID=1) or (ID=guid'<guidValue>')
            // If there are multiple Id values then, they are comma separated.));))
            if (idString.StartsWith("/"))
            {
                idString = idString.Substring(1);
            }

            // Make sure the ( and ) parenthesis exist.
            if (String.IsNullOrEmpty(idString) || idString[0] != '(' || idString[idString.Length - 1] != ')')
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.EntityIdFormatIsIncorrect, entity.ServiceMetadata.Id));
            }

            // Remove the ( and ) characters.
            idString = idString.Substring(1, idString.Length - 2);

            // Get the key properties for the entity.
            var keyFieldPropertyInfoList = ReflectionUtility.GetPrimaryKeysPropertyInfoMapping(entity.GetType());

            // Split the string and get individual keyvalue pair strings. They key and value are still a single string separated by '='.
            // for types such as Guid, the value will be prefixed with 'guid'.
            string[] primaryKeyValuePair = idString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            // Throw if there is a mismatch between the key count of the entity and that passed in the URI.
            if (primaryKeyValuePair.Length != keyFieldPropertyInfoList.Length)
            {
                throw SyncServiceException.CreateBadRequestError(String.Format(Strings.BadRequestKeyCountMismatch, entity.ServiceMetadata.Id, entity.GetType()));
            }

            // At this point, we have key value pairs of the form "ID=1".
            foreach (var keyValuePair in primaryKeyValuePair)
            {
                // example: ID=1
                string[] keyValue = keyValuePair.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);

                // Every key should have only 2 components.
                Debug.Assert(2 == keyValue.Length);

                // Get the property from the key list.
                string key          = keyValue[0].Trim();
                var    propertyInfo = keyFieldPropertyInfoList.Where(p => p.Name == key).FirstOrDefault();

                if (null == propertyInfo)
                {
                    throw SyncServiceException.CreateBadRequestError(
                              String.Format(Strings.BadRequestKeyNotFoundInResource, key, entity.ServiceMetadata.Id, entity.GetType()));
                }

                // Get typed value of the value.
                object targetValue;
                // Parse the value based on the target type.
                if (!ODataIdParser.TryKeyStringToPrimitive(keyValue[1], propertyInfo.PropertyType, out targetValue))
                {
                    throw SyncServiceException.CreateBadRequestError(
                              String.Format(Strings.UnableToParseKeyValueForProperty, keyValuePair, entity.ServiceMetadata.Id));
                }

                // Set the property value.
                propertyInfo.SetValue(entity, targetValue, null);
            }
        }
Example #19
0
        // This method ensures that we have a valid feed through the formatters. The BodyWriter delegate in WCF
        // seems to not recover from an unhandled exception caused by the formatters and sends out an empty response to the caller.
        // This is a workaround for this issue until we find a better solution.
        private SyncWriter GetSyncWriterWithContents()
        {
            var conflictEntryKeys = new List <string>();
            var errorEntryKeys    = new List <string>();
            var primaryKeyToIncomingEntitiesMapping = new Dictionary <string, IOfflineEntity>();

            // Save the mapping between entity PK string -> entity
            foreach (var entity in _incomingEntities)
            {
                string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity);
                if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey))
                {
                    throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest);
                }

                primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity);
            }

            if (_rejectedEntities != null)
            {
                foreach (var entity in _rejectedEntities.Keys)
                {
                    string primaryKey = ReflectionUtility.GetPrimaryKeyString(entity);
                    if (primaryKeyToIncomingEntitiesMapping.ContainsKey(primaryKey))
                    {
                        throw SyncServiceException.CreateInternalServerError(Strings.MultipleEntriesWithSamePrimaryKeyInIncomingRequest);
                    }

                    primaryKeyToIncomingEntitiesMapping.Add(primaryKey, entity);
                }
            }

            // Get the appropriate SyncWriter instance based on the serialization format.
            var oDataWriter = WebUtil.GetSyncWriter(_responseSerializationFormat, _baseUri);

            oDataWriter.StartFeed(_applyChangesResponse.IsLastBatch, _applyChangesResponse.ServerBlob);

            // Write conflict entities.
            foreach (var entity in _applyChangesResponse.Conflicts)
            {
                // Add the primary key string to the conflictEntryKey list.
                // The primary keys are the same for both Live and Losing entities.
                conflictEntryKeys.Add(ReflectionUtility.GetPrimaryKeyString(entity.LiveEntity));

                string tempId;

                // If the client change lost, then we need to set the Id property
                // only if the property was not null/empty in the incoming request.
                string entityId = WebUtil.GenerateOfflineEntityId(entity.LiveEntity);

                // Set the Id property of the Live entity (server's copy).
                entity.LiveEntity.ServiceMetadata.Id = entityId;

                // Set the Id property of the Losing entity to the incoming entity's Id value
                entity.LosingEntity.ServiceMetadata.Id = entityId;

                // get the original tempId. Null value is ok.
                _idToTempIdMapping.TryGetValue(entityId, out tempId);

                if (entity.Resolution == SyncConflictResolution.ServerWins)
                {
                    // The losing entity is the client's copy.

                    // When resolution is ServerWins, we only need to set the losing change tempId.
                    oDataWriter.AddConflictItem(entity.LiveEntity, null /*tempId*/, entity.LosingEntity, tempId, entity.Resolution);
                }
                // If the client change won, then just set the Id property since an insert would have succeeded.
                else
                {
                    // When resolution is ClientWins, we only need to set the LiveEntity tempId.
                    oDataWriter.AddConflictItem(entity.LiveEntity, tempId, entity.LosingEntity, null /* tempId */, entity.Resolution);
                }
            }

            // Write error entities.
            foreach (var syncError in _applyChangesResponse.Errors)
            {
                Debug.Assert(null != syncError.LiveEntity);
                Debug.Assert(null != syncError.ErrorEntity);

                string entityId = WebUtil.GenerateOfflineEntityId(syncError.LiveEntity);

                // Set the Id for Live and Losing entity.
                syncError.LiveEntity.ServiceMetadata.Id  = entityId;
                syncError.ErrorEntity.ServiceMetadata.Id = entityId;

                string primaryKeyString = ReflectionUtility.GetPrimaryKeyString(syncError.ErrorEntity);

                // Add the string to the error key list.
                errorEntryKeys.Add(primaryKeyString);

                string tempId;

                _idToTempIdMapping.TryGetValue(entityId, out tempId);

                oDataWriter.AddErrorItem(syncError.LiveEntity, syncError.ErrorEntity, tempId, syncError.Description);
            }

            // Write all the inserted records here by iterating over the _incomingNewInsertEntities list
            foreach (var entity in _incomingNewInsertEntities)
            {
                string entityTempId;

                // Get the tempId of the entity.
                _idToTempIdMapping.TryGetValue(WebUtil.GenerateOfflineEntityId(entity), out entityTempId);

                // Write the output to the SyncWriter.
                oDataWriter.AddItem(entity, entityTempId);
            }

            return(oDataWriter);
        }
Example #20
0
        /// <summary>
        /// Get the serialization format for the response based on the value of the HTTP Accept header.
        ///
        /// If $format is not specified then the format comes from the accept header.
        ///
        /// The order in which a response content-type is chosen is based on the
        /// incoming "Accept" header and the types that the service supports.
        /// According to the HTTP/1.1 Header Field Definitions RFC
        /// (http:///www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), an absence of the
        /// Accept header means that the client accepts all response types.
        ///
        /// Media ranges can be overridden by more specific media ranges, for example:
        /// both application/json and application/atom+xml would override */*.
        ///
        /// Depending on the service configuration application/atom+xml would override application/json
        /// if application/atom+xml if the default serialization format, and application/json would
        /// override application/atom+xml if the default serialization format is application/json.
        ///
        /// A client can also send a media range of the following type: application/*, which can be
        /// substituted for application/atom+xml or application/json depending on the service configuration.
        ///
        /// A. If the default configured serialization format is "application/atom+xml"
        ///
        ///  The formats in order of priority are:
        ///     1. application/atom+xml
        ///     2. application/json
        ///     3. application/* or */* substituted with application/atom+xml
        ///
        ///  Examples (order of accept headers doesn't matter):
        ///     "application/*" -> ATOM+XML
        ///     "application/*,application/JSON" -> JSON
        ///     "application/*,application/ATOM+XML" -> ATOM+XML
        ///     "application/*,application/ATOM+XML,application/JSON" -> ATOM+XML
        ///     "application/JSON" -> JSON
        ///     "application/ATOM+XML" -> ATOM+XML
        ///     "application/JSON,application/ATOM+XML" -> ATOM+XML
        ///
        /// B. If the default configured serialization format is "application/json"
        ///
        ///  The formats in order of priority are:
        ///     1. application/json
        ///     2. application/atom+xml
        ///     3. application/* or */* substituted with application/json
        ///
        ///  Examples (order of accept headers doesn't matter):
        ///     "application/*" -> JSON
        ///     "application/*,application/JSON" -> JSON
        ///     "application/*,application/ATOM+XML" -> ATOM+XML
        ///     "application/*,application/ATOM+XML,application/JSON" -> JSON
        ///     "application/JSON" -> JSON
        ///     "application/ATOM+XML" -> ATOM+XML
        ///     "application/JSON,application/ATOM+XML" -> JSON
        ///
        /// Note: headers from firefox need to be trimmed before we make a comparison.In other words the media range
        /// parameter as specified in the above RFC are ignored.
        /// </summary>
        /// <returns>Response serialization format</returns>
        internal SyncSerializationFormat GetOutputSerializationFormat(SyncSerializationFormat defaultSerializationFormat)
        {
            // Read $format from querystring first
            string formatQueryString = QueryStringCollection[SYNC_FORMAT_QUERYKEY];

            if (!String.IsNullOrEmpty(formatQueryString))
            {
                if (0 == String.Compare(formatQueryString.ToLowerInvariant(), "atom", StringComparison.InvariantCultureIgnoreCase))
                {
                    return(SyncSerializationFormat.ODataAtom);
                }

                if (0 == String.Compare(formatQueryString.ToLowerInvariant(), "json", StringComparison.InvariantCultureIgnoreCase))
                {
                    return(SyncSerializationFormat.ODataJson);
                }
            }
            else if (!String.IsNullOrEmpty(RequestAccept))
            {
                var header =
                    RequestAccept.ToLowerInvariant().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(h => h.Trim());

                SyncSerializationFormat?outputSerializationFormat = null;

                foreach (string headerString in header)
                {
                    // Media range followed by optional semi-column and accept-params ,
                    string[] headerStringParts = headerString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                    if (0 == headerStringParts.Length)
                    {
                        continue;
                    }

                    // Is this header application/atom+xml
                    if (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_ATOM, StringComparison.OrdinalIgnoreCase))
                    {
                        outputSerializationFormat = SyncSerializationFormat.ODataAtom;
                    }

                    // Is this header application/json
                    if (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_JSON, StringComparison.OrdinalIgnoreCase))
                    {
                        outputSerializationFormat = SyncSerializationFormat.ODataJson;
                    }

                    // If the default header has been set explicitly then no need to read other headers
                    if (outputSerializationFormat == defaultSerializationFormat)
                    {
                        break;
                    }

                    // Is this header application/* or */*
                    if ((null == outputSerializationFormat) &&
                        (headerStringParts[0].Equals(CONTENT_TYPE_APPLICATION_ANY) || headerStringParts[0].Equals(CONTENT_TYPE_ANY)))
                    {
                        // Do not exit the loop as this can be overwritten by an explicit json or atnm+xml header
                        outputSerializationFormat = defaultSerializationFormat;
                    }
                }

                if (null == outputSerializationFormat)
                {
                    throw SyncServiceException.CreateNotAcceptable(Strings.UnsupportedAcceptHeaderValue);
                }

                return(outputSerializationFormat.Value);
            }

            // return the default serialization format.
            return(defaultSerializationFormat);
        }