/// <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); }
/// <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); }
/// <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); }
/// <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; }
/// <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; } }
/// <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); }
/// <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); }
/// <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); }