Exemplo n.º 1
0
        /// <summary>
        /// Get the entity from the incoming request that matches the primary key string of the entity passed as a parameter.
        /// </summary>
        /// <param name="primaryKeyToIncomingEntitiesMapping">Dictionary of mapping between primary key and the actual entities from incoming request.</param>
        /// <param name="entity">Entity for which to search a match in the incoming request.</param>
        /// <param name="isConflict">Indicates if this is called during conflict processing. Used to select appropriate error messages.</param>
        /// <returns>Entity from the incoming request.</returns>
        private static IOfflineEntity GetEntityFromIncomingRequest(Dictionary <string, IOfflineEntity> primaryKeyToIncomingEntitiesMapping,
                                                                   IOfflineEntity entity,
                                                                   bool isConflict)
        {
            // find the actual entity from the input list.
            var entityListInRequest = primaryKeyToIncomingEntitiesMapping.Where(e =>
                                                                                e.Key.Equals(ReflectionUtility.GetPrimaryKeyString(entity), StringComparison.InvariantCultureIgnoreCase)).ToList();

            // If no match is found, then throw an error.
            if (0 == entityListInRequest.Count)
            {
                if (isConflict)
                {
                    throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest);
                }

                throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest);
            }

            // If the entity corresponding to the key is null, then throw an error.
            if (null == entityListInRequest[0].Value)
            {
                if (isConflict)
                {
                    throw SyncServiceException.CreateInternalServerError(Strings.ConflictEntityMissingInIncomingRequest);
                }

                throw SyncServiceException.CreateInternalServerError(Strings.ErrorEntityMissingInIncomingRequest);
            }

            return(entityListInRequest[0].Value);
        }
 /// <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())
     {
         // this method is called by InitializeService, which is invoked AFTER the creator. The TableGlobalNameMappings etc. are initialized during creator so we can use them to validate the parameters passed in
         // Validation of TableName
         if (0 == TableGlobalNameToTypeMapping.Where(m => 0 == String.Compare(m.Key, tableName)).Count())
         {
             // can not find the same tablename in the mapping
             throw SyncServiceException.CreateInternalServerError(Strings.InvalidTableNameForFilterParameters);
         }
         _filterParameters.Add(new SqlSyncProviderFilterParameterInfo
         {
             QueryStringKey   = queryStringParam.ToLowerInvariant(),
             SqlParameterName = sqlParameterName,
             TableName        = tableName,
             ValueType        = typeOfParam
         });
     }
     else
     {
         throw SyncServiceException.CreateInternalServerError(Strings.DuplicateFilterParameter);
     }
 }
Exemplo n.º 3
0
        /// <summary>
        /// Static constructor.
        /// </summary>
        static SyncService()
        {
            // Ensure that <T> has the SyncScope attribute on it.
            //Note: Early validation check to fail quickly if things are not correct.
            if (!Attribute.IsDefined(typeof(T), typeof(SyncScopeAttribute)))
            {
                throw SyncServiceException.CreateInternalServerError(Strings.TemplateClassNotMarkedWithSyncScopeAttribute);
            }

            SyncServiceTracer.TraceVerbose("SyncService initialized!");
        }
Exemplo n.º 4
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));
        }
Exemplo n.º 5
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);
            }
        }
        /// <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);
        }
 /// <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);
     }
 }
        /// <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;
                    }
                }
            }
        }
        /// <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);
            }
        }
Exemplo n.º 10
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);
        }