/**
         * Returns the ValueSet that corresponds to requestTime.
         */
        public IValueSet GetValues(ITime requestedTime, IQuantity quantity, IElementSet elementSet, PrefetchManager prefetchManager, ILink link)
        {
            try
            {
                // generate the key of the buffer entry we're looking for
                var key = ValueSetEntry.CreateKey(webServiceManager.FindServiceIdForQuantity(quantity.ID), quantity.ID, elementSet.ID, requestedTime, scenarioId);

                traceFile.Append("GetValues: " + key);

                var mapValueSet = client.getMap<string, ValueSetEntry>("valueSet");
                var mapElementSet = client.getMap<string, ElementSetEntry>("elementSet");
                var queueValueSetRequest = client.getQueue<ValueSetRequestEntry>("valueSetRequest");

                // record this request
                statistics.Add("GetValuesCount", 1);

                // measure our wait time
                var waitStopwatch = Stopwatch.StartNew();

                // see if the requested values are in the cache
                var valueSetEntry = mapValueSet.get(key);

                // if the value set does not exist then request it
                if (valueSetEntry == null)
                {
                    traceFile.Append("Requesting Value Set: " + key);

                    // insert the element set if necessary
                    if (elementSetsPut.Contains(elementSet.ID) == false)
                    {
                        elementSetsPut.Add(elementSet.ID);
                        mapElementSet.put(elementSet.ID, new ElementSetEntry(elementSet));
                    }

                    while (valueSetEntry == null)
                    {
                        // if we're prefetching, we may have already requested this
                        // value set in a previous prefetch that hasn't been fulfilled
                        // yet in which case we do not want to issue the request again.
                        if (prefetchManager.timeIsFetched(link, (TimeStamp)requestedTime) == false)
                        {
                            // insert the request into the queue
                            var insertRequestStopwatch = Stopwatch.StartNew();

                            // create the request entry
                            var valueSetRequestEntry = new ValueSetRequestEntry(webServiceManager.FindServiceIdForQuantity(quantity.ID), quantity.ID, elementSet.ID, Utils.ITimeToDateTime(requestedTime), scenarioId);

                            // BLOCKING
                            queueValueSetRequest.put(valueSetRequestEntry);

                            statistics.Add("RequestInsertTimeMS", insertRequestStopwatch.ElapsedMilliseconds);
                            traceFile.Append("RequestInsertTime:" + string.Format("{0:0.0}", insertRequestStopwatch.ElapsedMilliseconds) + "ms");
                        }

                        // PERFORMANCE STUDY: start delay at 100 and double each time
                        // we check and it's not available

                        // poll for the value set
                        var delay = 1000;
                        while (true)
                        {
                            // we know that the value set is not in the cache since
                            // we just checked that above, so wait immediately after
                            // making the request
                            Thread.Sleep(delay);

                            valueSetEntry = mapValueSet.get(key);
                            if (valueSetEntry != null)
                            {
                                break;
                            }
                            //delay *= 2;
                            traceFile.Append(string.Format("Waiting ({0}) For Value Set: ({1})", delay, key));

                            if (delay > 20000)
                            {
                                statistics.Add("RequestRetry", 1);
                                break;
                            }
                        }
                    }
                }

                statistics.Add("CacheWaitTimeMS", waitStopwatch.ElapsedMilliseconds);
                traceFile.Append("WaitTime:" + string.Format("{0:0.0}", waitStopwatch.ElapsedMilliseconds) + "ms");

                return new ScalarSet(valueSetEntry.Values());
            }
            catch (Exception e)
            {
                traceFile.Exception(e);
                return null;
            }
        }
        public void Update(ILink link)
        {
            // don't do anything if we're disabled
            if (isEnabled == false)
            {
                return;
            }

            // see when this quantity was last requested on this link
            var lastRequestedTime = prefetchMonitor.lastRequestedTime(link.ID, link.SourceQuantity.ID);

            // if it never has been requested, then no one needs this
            // yet, and we can't do any prefetching at all since we don't
            // know what time to start prefetching until we get the
            // first request
            if (lastRequestedTime.ModifiedJulianDay == 0)
            {
                return;
            }

            // there has been at least one request for values for this
            // quantity, so we should prefetch up to the limit
            var linkId = link.ID;
            var quantityId = link.SourceQuantity.ID;
            var elementSet = link.TargetElementSet;

            // start prefetching from the latest fetched time on this link
            var prefetchTime = prefetchMonitor.latestFetchTime(link);

            // see what the time limit is for how far we want to prefetch
            var prefetchLimit = prefetchMonitor.getTimeLimit();

            // prefetch up to the prefetch limit
            while (true)
            {
                // see what the next estimated request time after the latest
                // prefetch time is and what the prefetch limit is
                prefetchTime = prefetchMonitor.nextEstTimeReq(prefetchTime, linkId, quantityId);

                // stop when we pass the prefetch limit
                if (prefetchTime.ModifiedJulianDay > prefetchLimit.ModifiedJulianDay)
                {
                    break;
                }

                // see if we've already requested this time for this
                // link ourselves, in which case we don't need to bother checking
                if (prefetchMonitor.timeIsFetched(link, prefetchTime) == true)
                {
                    continue;
                }

                // log the prefetch status
                var prefetchCompletion = CalculateTimePercentage(prefetchMonitor.getTimeHorizon().Start, prefetchTime, prefetchLimit);
                traceFile.Append(string.Format("Prefetch:Link{0}/{1}:{2}-{3}:{4}%", linkId, quantityId, prefetchTime, prefetchLimit, (int)prefetchCompletion));

                // generate the key for the next time to prefetchh
                var key = ValueSetEntry.CreateKey(webServiceManager.FindServiceIdForQuantity(quantityId), quantityId, elementSet.ID, prefetchTime, scenarioId);

                // see if it's already in the buffer
                if (mapValueSet.containsKey(key) == true)
                {
                    // it must have been fetched by someone else, so update our state
                    prefetchMonitor.addFetchedTime(link, prefetchTime);
                    continue;
                }

                // create a request for the next time to prefetch
                var valueSetRequestEntry = new ValueSetRequestEntry(webServiceManager.FindServiceIdForQuantity(quantityId), quantityId, elementSet.ID, prefetchTime, scenarioId);

                // attempt to insert the request immediately (non-blocking)
                if (queueValueSetRequest.offer(valueSetRequestEntry) == true)
                {
                    traceFile.Append(string.Format("Prefetch:Requested:Link{0}/{1}:{2}", linkId, quantityId, prefetchTime));
                    statistics.Add("Prefetch:RequestCount", 1);

                    // remember that this time has been prefetched
                    prefetchMonitor.addFetchedTime(link, prefetchTime);
                }
                else
                {
                    traceFile.Append(string.Format("Prefetch:RequestOfferFailed:Link{0}/{1}:{2}", linkId, quantityId, prefetchTime));
                    statistics.Add("Prefetch:RequestOfferFail", 1);

                    // stop trying to prefetch if the request queue is full
                    break;
                }
            }
        }