public void Create(FeedEntry entry) { if (IsValid(entry)) { IFeedEntryEntityWrapper cWrapper = FeedEntryWrapperFactory.Create(_context.ResourceKind, _context); SdataTransactionResult trResult = cWrapper.Add(entry); if (trResult.HttpStatus == System.Net.HttpStatusCode.Created || trResult.HttpStatus == System.Net.HttpStatusCode.OK) { if (string.IsNullOrEmpty(entry.Id)) { entry.Id = trResult.Location; } _request.Response.FeedEntry = entry; _request.Response.Protocol.SendKnownResponseHeader(System.Net.HttpResponseHeader.Location, entry.Id); } else { _request.Response.StatusCode = trResult.HttpStatus; _request.Response.Diagnoses = new Diagnoses(); _request.Response.Diagnoses.Add(GetDiagnosis(trResult)); //throw new InvalidDataException(trResult.HttpStatus + ": " + trResult.HttpMessage); } } else { throw new RequestException("Please use valid single resource url"); } }
public CRUD(IRequest request) { _context = new RequestContext(request.Uri); _wrapper = FeedEntryWrapperFactory.Create(_context.ResourceKind, _context); _request = request; }
public void Update(FeedEntry entry) { // only atom entry supported!!! if (_request.ContentType != Sage.Common.Syndication.MediaType.AtomEntry) { throw new RequestException("Atom entry content type expected"); } // resource key must exist in url if (string.IsNullOrEmpty(_requestContext.ResourceKey)) { throw new RequestException("ResourceKey missing in requested url."); } string requestEndPointUrl; RequestContext entryResourceAsRequestContext; string url; // The url that references an existing resource string localId; // will be parsed from urlAttrValue and set to the key attribute of the result resource payload Guid uuid; // the new uuid // retrieve the feed entry //entry = FeedComponentFactory.Create<ILinkingResourceFeedEntry>(request.Stream); // Get the uuid from query uuid = (Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFrom(_requestContext.ResourceKey); if (null == entry) { throw new RequestException("sdata payload element missing"); } // the consumer MUST provide an sdata:url attribute that references an existing resource. url = entry.Uri; if (string.IsNullOrEmpty(url)) { throw new RequestException("sdata url attribute missing for resource payload element."); } // Parse the url of thew url attribute to get the local id. // Additionally we check for equality of the urls of the request url and // in the linked element up to the resourceKind. requestEndPointUrl = _requestContext.OriginEndPoint; entryResourceAsRequestContext = new RequestContext(new Sage.Common.Syndication.SDataUri(url)); // TODO: not really nice here. string linkedResourceUrl = entryResourceAsRequestContext.OriginEndPoint; if (!string.Equals(requestEndPointUrl, linkedResourceUrl, StringComparison.InvariantCultureIgnoreCase)) { throw new RequestException("Request url and linked entry resource not matching."); } string resourceKindName = _requestContext.ResourceKind.ToString(); localId = entryResourceAsRequestContext.ResourceKey; // The correlation store ICorrelatedResSyncInfoStore correlatedResSyncInfoStore = NorthwindAdapter.StoreLocator.GetCorrelatedResSyncStore(_requestContext.SdataContext); // update the correlation entry in the sync store. // if uuid is not in use -> throw exception // if resource is already linked -> remove existing link, reassign link // if resource is not yet linked -> reassign link CorrelatedResSyncInfo[] correlations; CorrelatedResSyncInfo correlationToModify = null; // retrieve the correlation we want to reassign by given uuid correlations = correlatedResSyncInfoStore.GetByUuid(resourceKindName, new Guid[] { uuid }); if (correlations.Length > 0) { correlationToModify = correlations[0]; } else { throw new RequestException("Uuid not in use"); } //remember the old Correlation for the new localId, will be deleted after the update. CorrelatedResSyncInfo[] oldCorrelations = correlatedResSyncInfoStore.GetByLocalId(resourceKindName, new string[] { localId }); // change the local ID to link to the new resource. // change the modification stamp and reset the tick correlationToModify.LocalId = localId; correlationToModify.ResSyncInfo.ModifiedStamp = DateTime.Now; correlationToModify.ResSyncInfo.Tick = 1; // update the correlation correlatedResSyncInfoStore.Update(resourceKindName, correlationToModify); //If updating went OK, delete the old correlation for the new localId (should be only 1, for-loop just to be sure to catch em all) foreach (CorrelatedResSyncInfo oldCorrelation in oldCorrelations) { correlatedResSyncInfoStore.Delete(resourceKindName, oldCorrelation.ResSyncInfo.Uuid); } // If the service consumer only needs to retrieve the URL, not the actual payload, // it may do so by adding an empty select parameter to its request: // a) select parameter not exist -> return deep resource payload // b) select exists and empty -> return no resource payload // c) select exists and not empty -> return deep resource payload as we do not yet support payload filtering // with select parameter. string tmpValue; // ?select bool includeResourcePayloads = true; // default value, but check for select parameter now if (_requestContext.SdataUri.QueryArgs.TryGetValue("select", out tmpValue)) { if (string.IsNullOrEmpty(_requestContext.SdataUri.QueryArgs["select"])) { includeResourcePayloads = false; } } // Create an entity wrapper if resource data should be requested. Otherwise // leave wrapper null. IFeedEntryEntityWrapper wrapper = null; //if (includeResourcePayloads) //TODO: Comment this in as soon as there is a possibility to send empty payload objects wrapper = FeedEntryWrapperFactory.Create(_requestContext.ResourceKind, _requestContext); /* Create the response entry */ _request.Response.FeedEntry = (FeedEntry)this.BuildFeedEntryForCorrelation(correlationToModify, wrapper); _request.Response.ContentType = MediaType.AtomEntry; }
public void Create(FeedEntry entry) { // only atom entry supported!!! if (_request.ContentType != Sage.Common.Syndication.MediaType.AtomEntry) { throw new RequestException("Atom entry content type expected"); } string requestEndPointUrl; RequestContext entryResourceAsRequestContext; string url; // The url that references an existing resource string localId; // will be parsed from urlAttrValue and set to the key attribute of the result resource payload Guid uuid; // the new uuid if (null == entry) { throw new RequestException("sdata payload element missing"); } // the consumer MUST provide an sdata:url attribute that references an existing resource. url = entry.Uri; if (string.IsNullOrEmpty(url)) { throw new RequestException("sdata url attribute missing for resource payload element."); } // Parse the url of thew url attribute to get the local id. // Additionally we check for equality of the urls of the request url and // in the linked element up to the resourceKind. requestEndPointUrl = _requestContext.OriginEndPoint; entryResourceAsRequestContext = new RequestContext(new Sage.Common.Syndication.SDataUri(url)); // TODO: not really nice here. string linkedResourceUrl = entryResourceAsRequestContext.OriginEndPoint; if (!string.Equals(requestEndPointUrl, linkedResourceUrl, StringComparison.InvariantCultureIgnoreCase)) { throw new RequestException("Request url and linked entry resource not matching."); } string resourceKindName = _requestContext.ResourceKind.ToString(); localId = entryResourceAsRequestContext.ResourceKey; ICorrelatedResSyncInfoStore correlatedResSyncInfoStore = NorthwindAdapter.StoreLocator.GetCorrelatedResSyncStore(_requestContext.SdataContext); CheckExisting(correlatedResSyncInfoStore, localId); // try to get the new uuid from uuid attribute // if this attribute is not set a new one is created if (null == entry.UUID || entry.UUID == Guid.Empty) { uuid = Guid.NewGuid(); } else { uuid = entry.UUID; } // store a new correlation entry to the sync store ResSyncInfo newResSyncInfo = new ResSyncInfo(uuid, requestEndPointUrl, 0, string.Empty, DateTime.Now); CorrelatedResSyncInfo newInfo = new CorrelatedResSyncInfo(localId, newResSyncInfo); correlatedResSyncInfoStore.Add(resourceKindName, newInfo); // If the service consumer only needs to retrieve the URL, not the actual payload, // it may do so by adding an empty select parameter to its request: // a) select parameter not exist -> return deep resource payload // b) select exists and empty -> return no resource payload // c) select exists and not empty -> return deep resource payload as we do not yet support payload filtering // with select parameter. string tmpValue; // ?select bool includeResourcePayloads = true; // default value, but check for select parameter now if (_requestContext.SdataUri.QueryArgs.TryGetValue("select", out tmpValue)) { if (string.IsNullOrEmpty(_requestContext.SdataUri.QueryArgs["select"])) { includeResourcePayloads = false; } } // Create an entity wrapper if resource data should be requested. Otherwise // leave wrapper null. IFeedEntryEntityWrapper wrapper = null; //if (includeResourcePayloads) //TODO: Comment this in as soon as there is a possibility to send empty payload objects wrapper = FeedEntryWrapperFactory.Create(_requestContext.ResourceKind, _requestContext); /* Create the response entry */ _request.Response.FeedEntry = (FeedEntry)this.BuildFeedEntryForCorrelation(newInfo, wrapper); _request.Response.ContentType = MediaType.AtomEntry; _request.Response.StatusCode = System.Net.HttpStatusCode.Created; _request.Response.Protocol.SendUnknownResponseHeader("location", FeedMetadataHelpers.BuildLinkedEntryUrl(_requestContext, uuid)); }
public void Read() { string resourceKindName; EntryRequestType entryRequestType; // Multi or Single bool includeResourcePayloads; ICorrelatedResSyncInfoStore correlatedResSyncStore; CorrelatedResSyncInfo[] correlatedResSyncInfos; int totalResult; // number of correlations found PageInfo normalizedPageInfo; // normalized pageInfo -> will be used for paging, etc. resourceKindName = _requestContext.ResourceKind.ToString(); // multi or single entry request? entryRequestType = (String.IsNullOrEmpty(_requestContext.ResourceKey)) ? EntryRequestType.Multi : EntryRequestType.Single; // get store to request the correlations correlatedResSyncStore = NorthwindAdapter.StoreLocator.GetCorrelatedResSyncStore(_requestContext.SdataContext); // receive correlated resource synchronization entries if (entryRequestType == EntryRequestType.Multi) { int pageNumber = PagingHelpers.GetPageNumber(_request.Uri.StartIndex, _request.Uri.Count); correlatedResSyncInfos = correlatedResSyncStore.GetPaged(resourceKindName, pageNumber, (int)(_request.Uri.Count ?? DEFAULT_ITEMS_PER_PAGE), out totalResult); } else { Guid uuid = (Guid)TypeDescriptor.GetConverter(typeof(Guid)).ConvertFrom(_requestContext.ResourceKey); correlatedResSyncInfos = correlatedResSyncStore.GetByUuid(resourceKindName, new Guid[] { uuid }); totalResult = correlatedResSyncInfos.Length; if (totalResult > 1) { throw new ApplicationException("More than one resource for uuid exists."); } if (totalResult == 0) { throw new RequestException("No resource found"); } } // If the service consumer only needs to retrieve the URL, not the actual payload, // it may do so by adding an empty select parameter to its request: // a) select parameter not exist -> return deep resource payload // b) select exists and empty -> return no resource payload // c) select exists and not empty -> return deep resource payload as we do not yet support payload filtering // with select parameter. string tmpValue; // ?select includeResourcePayloads = true; // default value, but check for select parameter now if (_requestContext.SdataUri.QueryArgs.TryGetValue("select", out tmpValue)) { if (string.IsNullOrEmpty(_requestContext.SdataUri.QueryArgs["select"])) { includeResourcePayloads = false; } } if (entryRequestType == EntryRequestType.Single) { IFeedEntryEntityWrapper wrapper = null; //if (includeResourcePayloads) //TODO: Comment this in as soon as there is a possibility to send empty payload objects wrapper = FeedEntryWrapperFactory.Create(_requestContext.ResourceKind, _requestContext); _request.Response.FeedEntry = this.BuildFeedEntryForCorrelation(correlatedResSyncInfos[0], wrapper); _request.Response.ContentType = MediaType.AtomEntry; } else { // Create an empty feed Feed <FeedEntry> feed = new Feed <FeedEntry>(); feed.Author = new FeedAuthor(); feed.Author.Name = "Northwind Adapter"; feed.Category = new FeedCategory("http://schemas.sage.com/sdata/categories", "collection", "Resource Collection"); DateTime updateTime = DateTime.Now; string feedUrl = _requestContext.SdataUri.ToString(); // the url requested string feedUrlWithoutQuery = (new Uri(feedUrl)).GetLeftPart(UriPartial.Path); // the url without query normalizedPageInfo = PagingHelpers.Normalize(_request.Uri.StartIndex, _request.Uri.Count, totalResult); IFeedEntryEntityWrapper wrapper = null; //if (includeResourcePayloads) //TODO: Comment this in as soon as there is a possibility to send empty payload objects wrapper = FeedEntryWrapperFactory.Create(_requestContext.ResourceKind, _requestContext); // create a feed entry for each correlation. IEnumerator <CorrelatedResSyncInfo> pagedCorrelationEnumerator = PagingHelpers.GetPagedEnumerator <CorrelatedResSyncInfo>(normalizedPageInfo, correlatedResSyncInfos); while (pagedCorrelationEnumerator.MoveNext()) { // Create and append a feed entry per each id FeedEntry entry = this.BuildFeedEntryForCorrelation(pagedCorrelationEnumerator.Current, wrapper); if (entry != null) { feed.Entries.Add(entry); } } // set feed title feed.Title = string.Format("{0} linked {1}", feed.Entries.Count, resourceKindName); // set feed update feed.Updated = updateTime; // set id feed.Id = feedUrl; // add links (general) feed.Links.AddRange(LinkFactory.CreateFeedLinks(_requestContext, feedUrl)); #region PAGING & OPENSEARCH // add links (paging) feed.Links.AddRange(LinkFactory.CreatePagingLinks(normalizedPageInfo, totalResult, feedUrlWithoutQuery)); /* OPENSEARCH */ feed.ItemsPerPage = normalizedPageInfo.Count; feed.StartIndex = normalizedPageInfo.StartIndex; feed.TotalResults = totalResult; #endregion _request.Response.Feed = feed; _request.Response.ContentType = MediaType.Atom; } }
// Asynchronous called method private void Execute(NorthwindConfig config, Digest targetDigest) { #region Declaration SdataContext sdataContext; SupportedResourceKinds resource; IAppBookmarkInfoStore appBookmarkInfoStore; ICorrelatedResSyncInfoStore correlatedResSyncInfoStore; ISyncSyncDigestInfoStore syncDigestStore; ISynctickProvider tickProvider; string resourceKind; string EndPoint; int nexttick = 1; Token lastToken; Token nextToken; Identity[] changedIdentites; IFeedEntryEntityWrapper wrapper; #endregion #region init sdataContext = _parentPerformer._requestContext.SdataContext; resource = _parentPerformer._requestContext.ResourceKind; resourceKind = resource.ToString(); EndPoint = _parentPerformer._requestContext.DatasetLink + resourceKind; appBookmarkInfoStore = NorthwindAdapter.StoreLocator.GetAppBookmarkStore(sdataContext); correlatedResSyncInfoStore = NorthwindAdapter.StoreLocator.GetCorrelatedResSyncStore(sdataContext); syncDigestStore = NorthwindAdapter.StoreLocator.GetSyncDigestStore(sdataContext); tickProvider = NorthwindAdapter.StoreLocator.GettickProvider(sdataContext); wrapper = FeedEntryWrapperFactory.Create(resource, _parentPerformer._requestContext); #endregion #region get last token or create a new one if (!appBookmarkInfoStore.Get <Token>(resourceKind, out lastToken)) { lastToken = new Token(); lastToken.InitRequest = true; } #endregion #region Get local identities of changed entries since last synchronisation changedIdentites = wrapper.Entity.GetLastChanges(lastToken, config, out nextToken); #endregion if (resource == SupportedResourceKinds.phoneNumbers) { #region workaround for phones for (int index = 0; index < changedIdentites.Length; index++) { string phoneid = changedIdentites[index].Id + Sage.Integration.Northwind.Application.API.Constants.PhoneIdPostfix; string faxId = changedIdentites[index].Id + Sage.Integration.Northwind.Application.API.Constants.FaxIdPostfix; // receive the resource payloads for the phone id and for the fax id // so that we can calculate their current etags. FeedEntry phoneResourcePayloadContainer = wrapper.GetFeedEntry(phoneid); FeedEntry faxResourcePayloadConatainer = wrapper.GetFeedEntry(faxId); string etag; CorrelatedResSyncInfo[] correlatedResSyncInfos; #region Phone if (phoneResourcePayloadContainer != null) { // calculate etag of the resource etag = EtagServices.ComputeEtag(phoneResourcePayloadContainer, true); // new etag // retrieve correlations for the current identity from synch storage correlatedResSyncInfos = correlatedResSyncInfoStore.GetByLocalId(resourceKind, new string[] { phoneid }); // if no correlation exists AND resource has been deleted: // -> continue with next id // else if no correlation exists: // -> get next tick, create new correlation and add correlation to synch store // otherwise and if the etag stored in synch store is different than etag previously calculated: // -> get next tick, modify existing correlation and update it in synch store. if (correlatedResSyncInfos.Length == 0 && phoneResourcePayloadContainer.IsDeleted) { continue; } else if (correlatedResSyncInfos.Length == 0) { nexttick = tickProvider.CreateNexttick(resourceKind); // create next tick ResSyncInfo resyncInfo = new ResSyncInfo(Guid.NewGuid(), EndPoint, nexttick, etag, DateTime.Now); CorrelatedResSyncInfo info = new CorrelatedResSyncInfo(phoneid, resyncInfo); correlatedResSyncInfoStore.Put(resourceKind, info); syncDigestStore.PersistNewer(resourceKind, info.ResSyncInfo); } else if (!correlatedResSyncInfos[0].ResSyncInfo.Etag.Equals(etag)) { nexttick = tickProvider.CreateNexttick(resourceKind); correlatedResSyncInfos[0].ResSyncInfo.Etag = etag; correlatedResSyncInfos[0].ResSyncInfo.Tick = nexttick; correlatedResSyncInfos[0].ResSyncInfo.EndPoint = EndPoint; correlatedResSyncInfos[0].ResSyncInfo.ModifiedStamp = DateTime.Now; correlatedResSyncInfoStore.Put(resourceKind, correlatedResSyncInfos[0]); syncDigestStore.PersistNewer(resourceKind, correlatedResSyncInfos[0].ResSyncInfo); } } #endregion #region Fax if (faxResourcePayloadConatainer != null) { // calculate etag of the resource etag = EtagServices.ComputeEtag(faxResourcePayloadConatainer, true); // new etag // retrieve correlations for the current identity from synch storage correlatedResSyncInfos = correlatedResSyncInfoStore.GetByLocalId(resourceKind, new string[] { faxId }); // if no correlation exists AND resource has been deleted: // -> continue with next id // else if no correlation exists: // -> get next tick, create new correlation and add correlation to synch store // otherwise and if the etag stored in synch store is different than etag previously calculated: // -> get next tick, modify existing correlation and update it in synch store. if (correlatedResSyncInfos.Length == 0 && faxResourcePayloadConatainer.IsDeleted) { continue; } else if (correlatedResSyncInfos.Length == 0) { nexttick = tickProvider.CreateNexttick(resourceKind); // create next tick ResSyncInfo resyncInfo = new ResSyncInfo(Guid.NewGuid(), EndPoint, nexttick, etag, DateTime.Now); CorrelatedResSyncInfo info = new CorrelatedResSyncInfo(faxId, resyncInfo); correlatedResSyncInfoStore.Put(resourceKind, info); syncDigestStore.PersistNewer(resourceKind, info.ResSyncInfo); } else if (!correlatedResSyncInfos[0].ResSyncInfo.Etag.Equals(etag)) { nexttick = tickProvider.CreateNexttick(resourceKind); correlatedResSyncInfos[0].ResSyncInfo.Etag = etag; correlatedResSyncInfos[0].ResSyncInfo.Tick = nexttick; correlatedResSyncInfos[0].ResSyncInfo.EndPoint = EndPoint; correlatedResSyncInfos[0].ResSyncInfo.ModifiedStamp = DateTime.Now; correlatedResSyncInfoStore.Put(resourceKind, correlatedResSyncInfos[0]); syncDigestStore.PersistNewer(resourceKind, correlatedResSyncInfos[0].ResSyncInfo); } } #endregion } #endregion } else { string id; FeedEntry resourcePayloadContainer; CorrelatedResSyncInfo[] correlatedResSyncInfos; string etag; // iterate through the collection of ids of all changed resources. for (int index = 0; index < changedIdentites.Length; index++) { // current id from iterated collection id = changedIdentites[index].Id; // get resource payload container for the current identity so that we can calculated the // etag. // continue with next identity if no resource was found. resourcePayloadContainer = wrapper.GetFeedEntry(id); // calculate etag of the current resource payload etag = EtagServices.ComputeEtag(resourcePayloadContainer, true); // retrieve correlations for the current identity from synch storage correlatedResSyncInfos = correlatedResSyncInfoStore.GetByLocalId(resourceKind, new string[] { id }); // if no correlation exists AND resource has been deleted: // -> continue with next id // else if no correlation exists: // -> get next tick, create new correlation and add correlation to synch store // otherwise and if the etag stored in synch store is different than etag previously calculated: // -> get next tick, modify existing correlation and update it in synch store. if (resourcePayloadContainer == null || (correlatedResSyncInfos.Length == 0 && resourcePayloadContainer.IsDeleted)) { continue; } else if (correlatedResSyncInfos.Length == 0) { nexttick = tickProvider.CreateNexttick(resourceKind); // create next tick ResSyncInfo resyncInfo = new ResSyncInfo(Guid.NewGuid(), EndPoint, nexttick, etag, DateTime.Now); CorrelatedResSyncInfo info = new CorrelatedResSyncInfo(id, resyncInfo); correlatedResSyncInfoStore.Put(resourceKind, info); syncDigestStore.PersistNewer(resourceKind, info.ResSyncInfo); } else if (!correlatedResSyncInfos[0].ResSyncInfo.Etag.Equals(etag)) { nexttick = tickProvider.CreateNexttick(resourceKind); correlatedResSyncInfos[0].ResSyncInfo.Etag = etag; correlatedResSyncInfos[0].ResSyncInfo.Tick = nexttick; correlatedResSyncInfos[0].ResSyncInfo.EndPoint = EndPoint; correlatedResSyncInfos[0].ResSyncInfo.ModifiedStamp = DateTime.Now; correlatedResSyncInfoStore.Put(resourceKind, correlatedResSyncInfos[0]); syncDigestStore.PersistNewer(resourceKind, correlatedResSyncInfos[0].ResSyncInfo); } } } #region store next token appBookmarkInfoStore.Put(resourceKind, nextToken); #endregion // set tracking phase lock (_parentPerformer._asyncStateObj) { _parentPerformer._asyncStateObj.Tracking.Phase = TrackingPhase.GETCHANGESBYtick; } if (null != targetDigest) { ICorrelatedResSyncInfoEnumerator enumerator; List <string> EndPoints = new List <string>(); #warning remove this workaround if (targetDigest.Entries != null) { foreach (DigestEntry targetDigestEntry in targetDigest.Entries) { EndPoints.Add(targetDigestEntry.EndPoint); enumerator = correlatedResSyncInfoStore.GetSincetick(resourceKind, targetDigestEntry.EndPoint, ((int)targetDigestEntry.Tick) - 1); while (enumerator.MoveNext()) { // No lock needed, as we expect that CorrelatedResSyncInfos list is // only acceeded anywhere else when Tracking phase is 'finish'. //lock(_parentPerformer._asyncStateObj) //{ _parentPerformer._asyncStateObj.CorrelatedResSyncInfos.Add(enumerator.Current); //} } } } SyncDigestInfo sourceSyncDigestInfo = syncDigestStore.Get(resourceKind); foreach (SyncDigestEntryInfo digestEntry in sourceSyncDigestInfo) { if (EndPoints.Contains(digestEntry.EndPoint)) { continue; } EndPoints.Add(digestEntry.EndPoint); enumerator = correlatedResSyncInfoStore.GetSincetick(resourceKind, digestEntry.EndPoint, -1); while (enumerator.MoveNext()) { // No lock needed, as we expect that CorrelatedResSyncInfos list is // only acceeded anywhere else when Tracking phase is 'finish'. //lock(_parentPerformer._asyncStateObj) //{ _parentPerformer._asyncStateObj.CorrelatedResSyncInfos.Add(enumerator.Current); //} } } if (!EndPoints.Contains(EndPoint)) { enumerator = correlatedResSyncInfoStore.GetSincetick(resourceKind, EndPoint, -1); while (enumerator.MoveNext()) { // No lock needed, as we expect that CorrelatedResSyncInfos list is // only acceeded anywhere else when Tracking phase is 'finish'. //lock(_parentPerformer._asyncStateObj) //{ _parentPerformer._asyncStateObj.CorrelatedResSyncInfos.Add(enumerator.Current); //} } } } // Set tracking phase to FINISH lock (_parentPerformer._asyncStateObj.Tracking) { _parentPerformer._asyncStateObj.Tracking.Phase = TrackingPhase.FINISH; } }
/// <summary> /// /// </summary> /// <param name="config"></param> /// <returns></returns> /// <remarks>This method is not threadsafe as the performer must be finished when calling this method.</remarks> public Feed <FeedEntry> GetFeed(NorthwindConfig config, PageInfo normalizedPageInfo) { Feed <FeedEntry> syncSourceFeed; SdataContext sdataContext; SupportedResourceKinds resource; string resourceKind; string EndPoint; Guid trackingId; List <CorrelatedResSyncInfo> correlatedResSyncInfos; sdataContext = _parentPerformer._requestContext.SdataContext; resource = _parentPerformer._requestContext.ResourceKind; resourceKind = resource.ToString(); correlatedResSyncInfos = _parentPerformer._asyncStateObj.CorrelatedResSyncInfos; EndPoint = _parentPerformer._requestContext.DatasetLink + resourceKind; trackingId = _parentPerformer._requestContext.TrackingId; //ISyncSyncDigestInfoStore syncDigestStore = RequestReceiver.NorthwindAdapter.StoreLocator.GetSyncDigestStore(sdataContext); ISyncSyncDigestInfoStore syncDigestStore = NorthwindAdapter.StoreLocator.GetSyncDigestStore(sdataContext); SyncDigestInfo syncDigestInfo = syncDigestStore.Get(resourceKind); // Create a new Feed instance syncSourceFeed = new Feed <FeedEntry>();//FeedComponentFactory.Create<ISyncSourceFeed>(); syncSourceFeed.Author = new FeedAuthor(); syncSourceFeed.Author.Name = "Northwind Adapter"; syncSourceFeed.Category = new FeedCategory("http://schemas.sage.com/sdata/categories", "collection", "Resource Collection"); #region Digest syncSourceFeed.Digest = new Digest(); syncSourceFeed.Digest.Entries = (syncDigestInfo == null) ? new DigestEntry[0] : new DigestEntry[syncDigestInfo.Count]; // set digest origin syncSourceFeed.Digest.Origin = _parentPerformer._requestContext.OriginEndPoint; if (syncDigestInfo != null) { // convert and set digest entries from synch store object to feed object for (int i = 0; i < syncDigestInfo.Count; i++) { syncSourceFeed.Digest.Entries[i] = new DigestEntry(); syncSourceFeed.Digest.Entries[i].ConflictPriority = syncDigestInfo[i].ConflictPriority; syncSourceFeed.Digest.Entries[i].EndPoint = syncDigestInfo[i].EndPoint; syncSourceFeed.Digest.Entries[i].Tick = (int)syncDigestInfo[i].Tick; syncSourceFeed.Digest.Entries[i].Stamp = DateTime.Now; } } #endregion #region Entries // retrieve the data connection wrapper IFeedEntryEntityWrapper wrapper = FeedEntryWrapperFactory.Create(resource, _parentPerformer._requestContext); IEnumerator <CorrelatedResSyncInfo> correlationEnumerator = PagingHelpers.GetPagedEnumerator <CorrelatedResSyncInfo>(normalizedPageInfo, correlatedResSyncInfos.ToArray()); while (correlationEnumerator.MoveNext()) { syncSourceFeed.Entries.Add(this.BuildFeedEntry(correlationEnumerator.Current, wrapper)); } #endregion // initialize the feed string feedUrl = string.Format("{0}/$syncSource('{1}')", EndPoint, trackingId); string feedUrlWithoutQuery = (new Uri(feedUrl)).GetLeftPart(UriPartial.Path); // the url without query // set id tag syncSourceFeed.Id = feedUrl; // set title tag syncSourceFeed.Title = string.Format("{0} synchronization source feed {1}", resourceKind.ToString(), trackingId); // set update // syncSourceFeed.Updated = DateTime.Now; // set syncMode syncSourceFeed.SyncMode = SyncMode.catchUp;// syncModeenum.catchUp; // add links (general) syncSourceFeed.Links.AddRange(LinkFactory.CreateFeedLinks(_parentPerformer._requestContext, feedUrl)); #region PAGING & OPENSEARCH // add links (paging) syncSourceFeed.Links.AddRange(LinkFactory.CreatePagingLinks(normalizedPageInfo, correlatedResSyncInfos.Count, feedUrlWithoutQuery)); /* OPENSEARCH */ syncSourceFeed.ItemsPerPage = normalizedPageInfo.Count; syncSourceFeed.StartIndex = normalizedPageInfo.StartIndex; syncSourceFeed.TotalResults = correlatedResSyncInfos.Count; #endregion return(syncSourceFeed); }
// Asynchronous called method private void Execute(NorthwindConfig config, IFeed feed, Digest sourceDigest) { #region Declarations SdataContext sdataContext; SupportedResourceKinds resource; IAppBookmarkInfoStore appBookmarkInfoStore; ICorrelatedResSyncInfoStore correlatedResSyncInfoStore; ISyncSyncDigestInfoStore syncDigestStore; GuidConverter guidConverter = new GuidConverter(); string resourceKind; string EndPoint; //Digest sourceDigest; SyncDigestInfo targetDigest; IFeedEntryEntityWrapper wrapper; #endregion #region init sdataContext = _parentPerformer._requestContext.SdataContext; resource = _parentPerformer._requestContext.ResourceKind; resourceKind = resource.ToString(); EndPoint = _parentPerformer._requestContext.DatasetLink + resourceKind; appBookmarkInfoStore = NorthwindAdapter.StoreLocator.GetAppBookmarkStore(sdataContext); correlatedResSyncInfoStore = NorthwindAdapter.StoreLocator.GetCorrelatedResSyncStore(sdataContext); syncDigestStore = NorthwindAdapter.StoreLocator.GetSyncDigestStore(sdataContext); //#warning implement this //sourceDigest = new Digest(); //sourceDigest = ((Feed<FeedEntry>)feed).Digest; targetDigest = syncDigestStore.Get(resourceKind); wrapper = FeedEntryWrapperFactory.Create(resource, _parentPerformer._requestContext); #endregion #region process entries bool sourceIsDeleteMode; SdataTransactionResult sdTrResult; SyncState sourceState; SyncState targetState; foreach (FeedEntry entry in feed.Entries) { sdTrResult = null; try { // Check whether the source entry had been deleted. // if not we expect a payload! // The variable 'sourceIsDeleteMode' holds the result of this check for later calls. if (entry.IsDeleted) { sourceIsDeleteMode = true; } /*else if (null == entry.Payload) * throw new Exception("Payload missing.");*/ else { sourceIsDeleteMode = false; } // get the source syncstate sourceState = entry.SyncState; string uuidString = entry.UUID.ToString(); Guid uuid = entry.UUID; // Look into the store to get all correlations of the uuid received from source CorrelatedResSyncInfo[] corelations = correlatedResSyncInfoStore.GetByUuid(resourceKind, new Guid[] { uuid }); if (corelations.Length == 0) { if (sourceIsDeleteMode) { // the source entry had been deleted and no correlation exists for this // entry in the target. // So we do nothing here. sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.OK; sdTrResult.HttpMessage = "OK"; sdTrResult.HttpMethod = HttpMethod.DELETE; sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; } else { // no target entry exists for the updated or added source entry. // So we add the source entry to the application and add a new correlation to the sync store. // If this operation fails we will have to set the result state to conflict. try { // add the entry to application sdTrResult = wrapper.Add(entry); if ((sdTrResult != null) && ((sdTrResult.HttpStatus == HttpStatusCode.OK) || (sdTrResult.HttpStatus == HttpStatusCode.Created))) { string etag = EtagServices.ComputeEtag(entry, true); ResSyncInfo resSyncInfo = new ResSyncInfo(uuid, entry.SyncState.EndPoint, (entry.SyncState.Tick > 0) ? entry.SyncState.Tick : 1, etag, entry.SyncState.Stamp); CorrelatedResSyncInfo correlatedResSyncInfo = new CorrelatedResSyncInfo(sdTrResult.LocalId, resSyncInfo); // store the new correlation to the sync store correlatedResSyncInfoStore.Put(resourceKind, correlatedResSyncInfo); // update the sync digest entry if the tick of the source entry is newer than the actual tick of the target. syncDigestStore.PersistNewer(resourceKind, correlatedResSyncInfo.ResSyncInfo); } } catch (Exception e) { // in case of an unexpected error while adding a new entry to target // we create a transaction result with the state 'Conflict' sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.Conflict; sdTrResult.HttpMethod = HttpMethod.POST; sdTrResult.HttpMessage = e.ToString(); sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; } } } else { string key = corelations[0].LocalId; // a correlation was found for the source entry. #region update or delete try { bool doUpdate = false; //bool doDelete = false; bool isConflict = true; // set the Key from correlation to the entry.Payload.Key property // only if source had not been deleted. if (!sourceIsDeleteMode) { entry.Key = corelations[0].LocalId; } targetState = Helper.GetSyncState(corelations[0]); //If sourceState.EndPoint = targetState.EndPoint, //there is no conflict and the update must be applied //if sourceState.Tick > targetState.tick. if (targetState.EndPoint.Equals(sourceState.EndPoint, StringComparison.InvariantCultureIgnoreCase)) { isConflict = false; if (sourceState.Tick > targetState.Tick) { //if (!sourceIsDeleteMode) doUpdate = true; //else // doDelete = true; } } else { SyncState sourceDigestSyncState = Helper.GetSyncState(sourceDigest, targetState.EndPoint);; SyncState targetDigestSyncState = Helper.GetSyncState(targetDigest, sourceState.EndPoint); //If targetState is contained in sourceDigest, //i.e. if sourceDigest has a digest entry E such that E.EndPoint = targetState.EndPoint //and E.Tick >= targetState.Tick, there is no conflict and the update must be applied. if (sourceDigestSyncState != null) { if (sourceDigestSyncState.Tick > targetState.Tick) { doUpdate = true; isConflict = false; } } //If sourceState is contained in targetDigest, //i.e. if targetDigest has a digest entry E such that E.EndPoint = sourceState.EndPoint //and E.Tick >= sourceState.Tick, there is no conflict and the update must be ignored //(target has the most recent version). if (targetDigestSyncState != null) { if (targetDigestSyncState.Tick > sourceState.Tick) { doUpdate = false; isConflict = false; } } //Otherwise (targetState not contained in sourceDigest, sourceState not contained in targetDigest), //there is a conflict. if ((sourceDigestSyncState == null) && (targetDigestSyncState == null)) { isConflict = true; } } //****************** Conflict **************** //In case of conflict, the target EndPoint uses the following algorithm to resolve the conflict: //Let sourceEntry be the sourceDigest digest entry such that sourceEntry.EndPoint = sourceState.EndPoint. //Let targetEntry be the targetDigest digest entry such that targetEntry.EndPoint = targetState.EndPoint. //If sourceEntry .conflictPriority <> targetEntry .conflictPriority, the side with lowest priority wins. if (isConflict) { int sourceConflictPriority = Helper.GetConflictPriority(sourceDigest, sourceState.EndPoint); int targetConflictPriority = Helper.GetConflictPriority(targetDigest, targetState.EndPoint); if (sourceConflictPriority > targetConflictPriority) { doUpdate = true; } else if (sourceConflictPriority < targetConflictPriority) { doUpdate = false; } else { //Otherwise (sourceEntry .conflictPriority = targetEntry .conflictPriority), //then sourceState.stamp and targetState.stamp are compared //and the entry with the most recent timestamp wins. if (sourceState.Stamp > targetState.Stamp) { doUpdate = true; } } } ResSyncInfo resSyncInfo = new ResSyncInfo(uuid, entry.SyncState.EndPoint, (entry.SyncState.Tick > 0)?entry.SyncState.Tick:1, "", entry.SyncState.Stamp); if (doUpdate && !sourceIsDeleteMode) { // update the entry in the application and update the sync store sdTrResult = wrapper.Update(entry); if ((sdTrResult != null) && (sdTrResult.HttpStatus == HttpStatusCode.OK)) { string etag = EtagServices.ComputeEtag(entry, true); resSyncInfo = new ResSyncInfo(uuid, entry.SyncState.EndPoint, (entry.SyncState.Tick > 0) ? entry.SyncState.Tick : 1, etag, entry.SyncState.Stamp); CorrelatedResSyncInfo correlatedResSyncInfo = new CorrelatedResSyncInfo(sdTrResult.LocalId, resSyncInfo); correlatedResSyncInfoStore.Put(resourceKind, correlatedResSyncInfo); } } else if (!doUpdate && !sourceIsDeleteMode) { sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.Conflict; sdTrResult.HttpMethod = HttpMethod.PUT; sdTrResult.HttpMessage = ""; sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; sdTrResult.LocalId = key; } else if (doUpdate && sourceIsDeleteMode) { // delete the entry in the application and update the sync store // we do not support nested deletion. So only TradingAccounts and SalesOrders can be deleted. if (resource == SupportedResourceKinds.tradingAccounts || resource == SupportedResourceKinds.salesOrders) { sdTrResult = wrapper.Delete(corelations[0].LocalId); if ((sdTrResult != null) && (sdTrResult.HttpStatus == HttpStatusCode.OK)) { correlatedResSyncInfoStore.Delete(resource.ToString(), corelations[0].ResSyncInfo.Uuid); } } else { // this does not correspond to real fact, what we did at target side! sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.OK; sdTrResult.HttpMessage = "OK"; sdTrResult.HttpMethod = HttpMethod.DELETE; sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; sdTrResult.LocalId = key; } } else { sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.Conflict; sdTrResult.HttpMessage = ""; sdTrResult.HttpMethod = HttpMethod.DELETE; sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; sdTrResult.LocalId = key; } syncDigestStore.PersistNewer(resourceKind, resSyncInfo); } catch (Exception e) { sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.Conflict; sdTrResult.HttpMethod = HttpMethod.PUT; sdTrResult.HttpMessage = e.ToString(); sdTrResult.ResourceKind = resource; sdTrResult.Uuid = uuidString; sdTrResult.LocalId = key; } #endregion } } catch (Exception e) { sdTrResult = new SdataTransactionResult(); sdTrResult.HttpStatus = HttpStatusCode.Conflict; sdTrResult.HttpMessage = e.ToString(); sdTrResult.ResourceKind = resource; //sdTrResult.Uuid = entry.Payload.PayloadContainer.Uuid; } #region store transaction result if (sdTrResult != null) { lock (_parentPerformer._asyncStateObj) { this._parentPerformer._asyncStateObj.TransactionResults.Add(sdTrResult); } } #endregion } #endregion // Set tracking phase lock (_parentPerformer._asyncStateObj.Tracking) { _parentPerformer._asyncStateObj.Tracking.Phase = TrackingPhase.FINISH; } }
/// <summary> /// /// </summary> /// <param name="config"></param> /// <returns></returns> /// <remarks>This method is not threadsafe as the performer must be finished when calling this method.</remarks> public Feed <FeedEntry> GetFeed(NorthwindConfig config, PageInfo normalizedPageInfo) { Feed <FeedEntry> syncTargetFeed = new Feed <FeedEntry>(); syncTargetFeed.Author = new FeedAuthor(); syncTargetFeed.Author.Name = "Northwind Adapter"; syncTargetFeed.Category = new FeedCategory("http://schemas.sage.com/sdata/categories", "collection", "Resource Collection"); SdataContext sdataContext; SupportedResourceKinds resource; string resourceKind; string EndPoint; Guid trackingId; List <SdataTransactionResult> transactinResults; sdataContext = _parentPerformer._requestContext.SdataContext; resource = _parentPerformer._requestContext.ResourceKind; resourceKind = resource.ToString(); transactinResults = _parentPerformer._asyncStateObj.TransactionResults; EndPoint = _parentPerformer._requestContext.DatasetLink + resourceKind;; trackingId = _parentPerformer._requestContext.TrackingId; // Create a new Feed instance // retrieve the data connection wrapper IFeedEntryEntityWrapper wrapper = FeedEntryWrapperFactory.Create(resource, _parentPerformer._requestContext); IEnumerator <SdataTransactionResult> transactionResultEnumerator = PagingHelpers.GetPagedEnumerator <SdataTransactionResult>(normalizedPageInfo, transactinResults.ToArray()); while (transactionResultEnumerator.MoveNext()) { syncTargetFeed.Entries.Add(this.BuildFeedEntry(transactionResultEnumerator.Current, wrapper)); } // initialize the feed string feedUrl = string.Format("{0}/$syncTarget('{1}')", EndPoint, trackingId); string feedUrlWithoutQuery = (new Uri(feedUrl)).GetLeftPart(UriPartial.Path); // the url without query // set id tag syncTargetFeed.Id = feedUrl; // set title tag syncTargetFeed.Title = string.Format("{0} synchronization target feed {1}", resourceKind.ToString(), trackingId); // set update #warning implement this //syncTargetFeed.Updated = DateTime.Now; // set syncMode syncTargetFeed.SyncMode = SyncMode.catchUp; // add links (general) syncTargetFeed.Links.AddRange(LinkFactory.CreateFeedLinks(_parentPerformer._requestContext, feedUrl)); #region PAGING & OPENSEARCH // add links (paging) syncTargetFeed.Links.AddRange(LinkFactory.CreatePagingLinks(normalizedPageInfo, transactinResults.Count, feedUrlWithoutQuery)); /* OPENSEARCH */ syncTargetFeed.ItemsPerPage = normalizedPageInfo.Count; syncTargetFeed.StartIndex = normalizedPageInfo.StartIndex; syncTargetFeed.TotalResults = transactinResults.Count; #endregion return(syncTargetFeed); }