Пример #1
0
        /// <summary>
        /// Check Metadata
        /// </summary>
        private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId)
        {
            // Check service ID
            if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id))
            {
                throw new CacheControllerException(
                          string.Format("Service did not return a permanent Id for tempId '{0}'", tempId));
            }

            // If an entity has a temp id then it should not be a tombstone
            if (entity.GetServiceMetadata().IsTombstone)
            {
                throw new CacheControllerException(string.Format(
                                                       "Service returned a tempId '{0}' in tombstoned entity.", tempId));
            }

            // Check that the tempId was sent by client
            if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId))
            {
                throw new CacheControllerException(
                          "Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId);
            }

            // Once received, remove the tempId from the mapping list.
            wrapper.TempIdToEntityMapping.Remove(tempId);
        }
Пример #2
0
        /// <summary>
        /// Called to add a particular Entity
        /// </summary>
        /// <param name="entry">Entity to add to serialize to the stream</param>
        /// <param name="tempId">TempId for the Entity</param>
        /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        public virtual void AddItem(IOfflineEntity entry, string tempId, bool emitMetadataOnly)
        {
            if (entry == null)
            {
                throw new ArgumentNullException("entry");
            }

            if (string.IsNullOrEmpty(entry.GetServiceMetadata().Id) && entry.GetServiceMetadata().IsTombstone)
            {
                // Skip sending tombstones that dont have an Id as these were local create + delete.
                return;
            }
            WriteItemInternal(entry, tempId, null /*conflicting*/, null /*conflictingTempId*/, null /*desc*/, false /*isconflict*/, emitMetadataOnly);
        }
Пример #3
0
        /// <summary>
        /// Called to add a particular Entity
        /// </summary>
        /// <param name="entry">Entity to add to serialize to the stream</param>
        /// <param name="tempId">TempId for the Entity</param>
        /// <param name="emitMetadataOnly">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        public virtual void AddItem(IOfflineEntity entry, string tempId, bool emitMetadataOnly)
        {
            if (entry == null)
            {
                
                throw new ArgumentNullException("entry");
            }

            if (string.IsNullOrEmpty(entry.GetServiceMetadata().Id) && entry.GetServiceMetadata().IsTombstone)
            {
                // Skip sending tombstones that dont have an Id as these were local create + delete.
                return;
            }
            WriteItemInternal(entry, tempId, null /*conflicting*/, null/*conflictingTempId*/, null /*desc*/, false /*isconflict*/, emitMetadataOnly);
        }
Пример #4
0
        /// <summary>
        /// Writes the <entry/> tag and all its related elements.
        /// </summary>
        /// <param name="live">Actual entity whose value is to be emitted.</param>
        /// <param name="tempId">The temporary Id if any</param>
        /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        /// <returns>XElement representation of the entry element</returns>
        private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial)
        {
            string typeName = live.GetType().FullName;

            if (!live.GetServiceMetadata().IsTombstone)
            {
                XElement entryElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubEntryElementName);

                // Add Etag
                if (!string.IsNullOrEmpty(live.GetServiceMetadata().ETag))
                {
                    entryElement.Add(new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.EtagElementName, live.GetServiceMetadata().ETag));
                }

                // Add TempId element
                if (!string.IsNullOrEmpty(tempId))
                {
                    entryElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.TempIdElementName, tempId));
                }

                // Add Id element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubIdElementName,
                    string.IsNullOrEmpty(live.GetServiceMetadata().Id) ? string.Empty : live.GetServiceMetadata().Id));

                // Add title element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubTitleElementName,
                        new XAttribute(FormatterConstants.AtomPubTypeElementName, "text")));

                // Add updated element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubUpdatedElementName,
                            XmlConvert.ToString(DateTime.Now)));

                // Add author element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubAuthorElementName,
                            new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubNameElementName)));

                // Write the <link> element
                entryElement.Add(
                    new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubLinkElementName,
                        new XAttribute(FormatterConstants.AtomPubRelAttrName, FormatterConstants.AtomPubEditLinkAttributeName),
                        new XAttribute(FormatterConstants.AtomPubTitleElementName, typeName),
                        new XAttribute(FormatterConstants.AtomPubHrefAttrName,
                            (live.GetServiceMetadata().EditUri != null) ? live.GetServiceMetadata().EditUri.ToString() : string.Empty)));

                // Write the <category> element
                entryElement.Add(
                    new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubCategoryElementName,
                        new XAttribute(FormatterConstants.AtomPubTermAttrName, live.GetType().FullName),
                        new XAttribute(FormatterConstants.AtomPubSchemaAttrName, FormatterConstants.ODataSchemaNamespace)));

                XElement contentElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubContentElementName);

                if (!emitPartial)
                {
                    // Write the entity contents
                    contentElement.Add(WriteEntityContents(live));
                }

                // Add the contents entity to the outer entity.
                entryElement.Add(contentElement);

                return entryElement;
            }
            
            // Write the at:deleted-entry tombstone element
            XElement tombstoneElement = new XElement(FormatterConstants.AtomDeletedEntryNamespace + FormatterConstants.AtomDeletedEntryElementName);
            tombstoneElement.Add(new XElement(FormatterConstants.AtomNamespaceUri + FormatterConstants.AtomReferenceElementName, live.GetServiceMetadata().Id));
            tombstoneElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.AtomPubCategoryElementName, typeName));
            return tombstoneElement;
        }
Пример #5
0
        /// <summary>
        /// Callback for the Upload HttpWebRequest.BeginGetResponse call
        /// </summary>
        private async Task ReadUploadResponse(HttpWebResponse response, AsyncArgsWrapper wrapper)
        {
            try
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    using (Stream responseStream = GetWrappedStream(response))
                    {
                        using (var syncReader = (SerializationFormat == SerializationFormat.ODataAtom)
                            ? new ODataAtomReader(responseStream, this.knownTypes)
                            : (SyncReader) new ODataJsonReader(responseStream, this.knownTypes))
                        {
                            // Read the response
                            await Task.Factory.StartNew(() =>
                            {
                                while (syncReader.Next())
                                {
                                    switch (syncReader.ItemType)
                                    {
                                    case ReaderItemType.Entry:
                                        IOfflineEntity entity      = syncReader.GetItem();
                                        IOfflineEntity ackedEntity = entity;
                                        string tempId = null;

                                        // If conflict only one temp ID should be set
                                        if (syncReader.HasTempId() && syncReader.HasConflictTempId())
                                        {
                                            throw new CacheControllerException(
                                                string.Format(
                                                    "Service returned a TempId '{0}' in both live and conflicting entities.",
                                                    syncReader.GetTempId()));
                                        }

                                        // Validate the live temp ID if any, before adding anything to the offline context
                                        if (syncReader.HasTempId())
                                        {
                                            tempId = syncReader.GetTempId();
                                            CheckEntityServiceMetadataAndTempIds(wrapper, entity, tempId);
                                        }

                                        //  If conflict
                                        if (syncReader.HasConflict())
                                        {
                                            Conflict conflict             = syncReader.GetConflict();
                                            IOfflineEntity conflictEntity = (conflict is SyncConflict)
                                                                                    ? ((SyncConflict)conflict).LosingEntity
                                                                                    : ((SyncError)conflict).ErrorEntity;

                                            // Validate conflict temp ID if any
                                            if (syncReader.HasConflictTempId())
                                            {
                                                tempId = syncReader.GetConflictTempId();
                                                CheckEntityServiceMetadataAndTempIds(wrapper, conflictEntity, tempId);
                                            }

                                            // Add conflict
                                            wrapper.UploadResponse.AddConflict(conflict);

                                            //
                                            // If there is a conflict and the tempId is set in the conflict entity then the client version lost the
                                            // conflict and the live entity is the server version (ServerWins)
                                            //
                                            if (syncReader.HasConflictTempId() && entity.GetServiceMetadata().IsTombstone)
                                            {
                                                //
                                                // This is a ServerWins conflict, or conflict error. The winning version is a tombstone without temp Id
                                                // so there is no way to map the winning entity with a temp Id. The temp Id is in the conflict so we are
                                                // using the conflict entity, which has the PK, to build a tombstone entity used to update the offline context
                                                //
                                                // In theory, we should copy the service metadata but it is the same end result as the service fills in
                                                // all the properties in the conflict entity
                                                //

                                                // Add the conflict entity
                                                conflictEntity.GetServiceMetadata().IsTombstone = true;
                                                ackedEntity = conflictEntity;
                                            }
                                        }

                                        // Add ackedEntity to storage. If ackedEntity is still equal to entity then add non-conflict entity.
                                        if (!String.IsNullOrEmpty(tempId))
                                        {
                                            wrapper.UploadResponse.AddUpdatedItem(ackedEntity);
                                        }
                                        break;

                                    case ReaderItemType.SyncBlob:
                                        wrapper.UploadResponse.ServerBlob = syncReader.GetServerBlob();
                                        break;
                                    }
                                }
                            });


                            if (wrapper.TempIdToEntityMapping != null && wrapper.TempIdToEntityMapping.Count != 0)
                            {
                                // The client sent some inserts which werent ack'd by the service. Throw.
                                var builder =
                                    new StringBuilder(
                                        "Server did not acknowledge with a permanent Id for the following tempId's: ");
                                builder.Append(string.Join(",", wrapper.TempIdToEntityMapping.Keys.ToArray()));
                                throw new CacheControllerException(builder.ToString());
                            }
                        }
                    }
                }
                else
                {
                    wrapper.UploadResponse.Error = new CacheControllerException(
                        string.Format("Remote service returned error status. Status: {0}, Description: {1}",
                                      response.StatusCode,
                                      response.StatusDescription));
                }
            }
            catch (Exception e)
            {
                if (ExceptionUtility.IsFatal(e))
                {
                    throw;
                }

                wrapper.Error = e;
            }
        }
Пример #6
0
        /// <summary>
        /// Writes the Json object tag and all its related elements.
        /// </summary>
        /// <param name="live">Actual entity whose value is to be emitted.</param>
        /// <param name="entryElement">This is the parent entry element that is needs to go in to. Will be null for regular items and non null for 
        /// conflict/error items only</param>
        /// <param name="tempId">The tempId for the element if passed in by the client.</param>
        /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        /// <returns>XElement representation of the entry element</returns>
        private XElement WriteEntry(IOfflineEntity live, XElement entryElement, string tempId, bool emitPartial)
        {
            string typeName = live.GetType().FullName;

            if (entryElement == null)
            {
                entryElement = new XElement("item",
                   new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object));
            }

            // Write the _metadata object for this entry
            XElement entryMetadata = new XElement(FormatterConstants.JsonSyncEntryMetadataElementName,
                new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object));

            // Add the tempId to metadata
            if (!string.IsNullOrEmpty(tempId))
            {
                entryMetadata.Add(new XElement(FormatterConstants.TempIdElementName,
                    new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), tempId));
            }

            // Add the uri to metadata
            entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryUriElementName,
                new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String),
                string.IsNullOrEmpty(live.GetServiceMetadata().Id) ? string.Empty : live.GetServiceMetadata().Id));

            // Add the etag to metadata
            if (!string.IsNullOrEmpty(live.GetServiceMetadata().ETag))
            {
                entryMetadata.Add(new XElement(FormatterConstants.EtagElementName,
                    new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.GetServiceMetadata().ETag));
            }

            // Add the edituri to metadata
            if (live.GetServiceMetadata().EditUri != null)
            {
                entryMetadata.Add(new XElement(FormatterConstants.EditUriElementName,
                    new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.GetServiceMetadata().EditUri));
            }

            // Add the type to metadata
            entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryTypeElementName,
                new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String),
                typeName));

            // Write the tombstone element
            if (live.GetServiceMetadata().IsTombstone)
            {
                entryMetadata.Add(new XElement(FormatterConstants.IsDeletedElementName,
                    new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Boolean),
                    true));
            }
            else if (!emitPartial)
            {
                // Write the entity contents only when its not a tombstone
                WriteEntityContentsToElement(entryElement, live);
            }

            // Add the metadata to the entry element
            entryElement.Add(entryMetadata);

            return entryElement;
        }
        /// <summary>
        /// Check Metadata
        /// </summary>
        private void CheckEntityServiceMetadataAndTempIds(AsyncArgsWrapper wrapper, IOfflineEntity entity, string tempId)
        {
            // Check service ID 
            if (string.IsNullOrEmpty(entity.GetServiceMetadata().Id))
                throw new CacheControllerException(
                    string.Format("Service did not return a permanent Id for tempId '{0}'", tempId));

            // If an entity has a temp id then it should not be a tombstone                
            if (entity.GetServiceMetadata().IsTombstone)
                throw new CacheControllerException(string.Format(
                    "Service returned a tempId '{0}' in tombstoned entity.", tempId));

            // Check that the tempId was sent by client
            if (!wrapper.TempIdToEntityMapping.ContainsKey(tempId))
                throw new CacheControllerException(
                    "Service returned a response for a tempId which was not uploaded by the client. TempId: " + tempId);

            // Once received, remove the tempId from the mapping list.
            wrapper.TempIdToEntityMapping.Remove(tempId);
        }
Пример #8
0
        /// <summary>
        /// Writes the <entry/> tag and all its related elements.
        /// </summary>
        /// <param name="live">Actual entity whose value is to be emitted.</param>
        /// <param name="tempId">The temporary Id if any</param>
        /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        /// <returns>XElement representation of the entry element</returns>
        private XElement WriteEntry(IOfflineEntity live, string tempId, bool emitPartial)
        {
            string typeName = live.GetType().FullName;

            if (!live.GetServiceMetadata().IsTombstone)
            {
                XElement entryElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubEntryElementName);

                // Add Etag
                if (!string.IsNullOrEmpty(live.GetServiceMetadata().ETag))
                {
                    entryElement.Add(new XAttribute(FormatterConstants.ODataMetadataNamespace + FormatterConstants.EtagElementName, live.GetServiceMetadata().ETag));
                }

                // Add TempId element
                if (!string.IsNullOrEmpty(tempId))
                {
                    entryElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.TempIdElementName, tempId));
                }

                // Add Id element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubIdElementName,
                                              string.IsNullOrEmpty(live.GetServiceMetadata().Id) ? string.Empty : live.GetServiceMetadata().Id));

                // Add title element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubTitleElementName,
                                              new XAttribute(FormatterConstants.AtomPubTypeElementName, "text")));

                // Add updated element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubUpdatedElementName,
                                              XmlConvert.ToString(DateTime.Now)));

                // Add author element
                entryElement.Add(new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubAuthorElementName,
                                              new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubNameElementName)));

                // Write the <link> element
                entryElement.Add(
                    new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubLinkElementName,
                                 new XAttribute(FormatterConstants.AtomPubRelAttrName, FormatterConstants.AtomPubEditLinkAttributeName),
                                 new XAttribute(FormatterConstants.AtomPubTitleElementName, typeName),
                                 new XAttribute(FormatterConstants.AtomPubHrefAttrName,
                                                (live.GetServiceMetadata().EditUri != null) ? live.GetServiceMetadata().EditUri.ToString() : string.Empty)));

                // Write the <category> element
                entryElement.Add(
                    new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubCategoryElementName,
                                 new XAttribute(FormatterConstants.AtomPubTermAttrName, live.GetType().FullName),
                                 new XAttribute(FormatterConstants.AtomPubSchemaAttrName, FormatterConstants.ODataSchemaNamespace)));

                XElement contentElement = new XElement(FormatterConstants.AtomXmlNamespace + FormatterConstants.AtomPubContentElementName);

                if (!emitPartial)
                {
                    // Write the entity contents
                    contentElement.Add(WriteEntityContents(live));
                }

                // Add the contents entity to the outer entity.
                entryElement.Add(contentElement);

                return(entryElement);
            }

            // Write the at:deleted-entry tombstone element
            XElement tombstoneElement = new XElement(FormatterConstants.AtomDeletedEntryNamespace + FormatterConstants.AtomDeletedEntryElementName);

            tombstoneElement.Add(new XElement(FormatterConstants.AtomNamespaceUri + FormatterConstants.AtomReferenceElementName, live.GetServiceMetadata().Id));
            tombstoneElement.Add(new XElement(FormatterConstants.SyncNamespace + FormatterConstants.AtomPubCategoryElementName, typeName));
            return(tombstoneElement);
        }
Пример #9
0
        /// <summary>
        /// Writes the Json object tag and all its related elements.
        /// </summary>
        /// <param name="live">Actual entity whose value is to be emitted.</param>
        /// <param name="entryElement">This is the parent entry element that is needs to go in to. Will be null for regular items and non null for
        /// conflict/error items only</param>
        /// <param name="tempId">The tempId for the element if passed in by the client.</param>
        /// <param name="emitPartial">Bool flag that denotes whether a partial metadata only entity is to be written</param>
        /// <returns>XElement representation of the entry element</returns>
        private XElement WriteEntry(IOfflineEntity live, XElement entryElement, string tempId, bool emitPartial)
        {
            string typeName = live.GetType().FullName;

            if (entryElement == null)
            {
                entryElement = new XElement("item",
                                            new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object));
            }

            // Write the _metadata object for this entry
            XElement entryMetadata = new XElement(FormatterConstants.JsonSyncEntryMetadataElementName,
                                                  new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Object));

            // Add the tempId to metadata
            if (!string.IsNullOrEmpty(tempId))
            {
                entryMetadata.Add(new XElement(FormatterConstants.TempIdElementName,
                                               new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), tempId));
            }

            // Add the uri to metadata
            entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryUriElementName,
                                           new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String),
                                           string.IsNullOrEmpty(live.GetServiceMetadata().Id) ? string.Empty : live.GetServiceMetadata().Id));

            // Add the etag to metadata
            if (!string.IsNullOrEmpty(live.GetServiceMetadata().ETag))
            {
                entryMetadata.Add(new XElement(FormatterConstants.EtagElementName,
                                               new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.GetServiceMetadata().ETag));
            }

            // Add the edituri to metadata
            if (live.GetServiceMetadata().EditUri != null)
            {
                entryMetadata.Add(new XElement(FormatterConstants.EditUriElementName,
                                               new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String), live.GetServiceMetadata().EditUri));
            }

            // Add the type to metadata
            entryMetadata.Add(new XElement(FormatterConstants.JsonSyncEntryTypeElementName,
                                           new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.String),
                                           typeName));

            // Write the tombstone element
            if (live.GetServiceMetadata().IsTombstone)
            {
                entryMetadata.Add(new XElement(FormatterConstants.IsDeletedElementName,
                                               new XAttribute(FormatterConstants.JsonTypeAttributeName, JsonElementTypes.Boolean),
                                               true));
            }
            else if (!emitPartial)
            {
                // Write the entity contents only when its not a tombstone
                WriteEntityContentsToElement(entryElement, live);
            }

            // Add the metadata to the entry element
            entryElement.Add(entryMetadata);

            return(entryElement);
        }