File: MetadataCache.cs

package info (click to toggle)
mono 6.14.1%2Bds2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,282,732 kB
  • sloc: cs: 11,182,461; xml: 2,850,281; ansic: 699,123; cpp: 122,919; perl: 58,604; javascript: 30,841; asm: 21,845; makefile: 19,602; sh: 10,973; python: 4,772; pascal: 925; sql: 859; sed: 16; php: 1
file content (872 lines) | stat: -rw-r--r-- 43,008 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
//---------------------------------------------------------------------
// <copyright file="MetadataCache.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------

namespace System.Data.Metadata.Edm
{
    using System;
    using System.Collections.Generic;
    using System.Data.Common.Utils;
    using System.Data.Entity;
    using System.Data.Mapping;
    using System.Diagnostics;
    using System.Runtime.Versioning;
    using System.Security.Permissions;
    using System.Threading;
    using System.Xml;

    /// <summary>
    /// Runtime Metadata Cache - this class contains the metadata cache entry for edm and store collections.
    /// </summary>
    internal static class MetadataCache
    {
        #region Fields

        private const string s_dataDirectory = "|datadirectory|";
        private const string s_metadataPathSeparator = "|";

        // This is the period in the periodic cleanup measured in milliseconds
        private const int cleanupPeriod = 5 * 60 * 1000;

        // This dictionary contains the cache entry for the edm item collection. The reason why we need to keep a seperate dictionary 
        // for CSpace item collection is that the same model can be used for different providers. We don't want to load the model
        // again and again
        private static readonly Dictionary<string, EdmMetadataEntry> _edmLevelCache = new Dictionary<string, EdmMetadataEntry>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// This dictionary contains the store cache entry - this entry will only keep track of StorageMappingItemCollection, since internally
        /// storage mapping item collection keeps strong references to both edm item collection and store item collection.
        /// </summary>
        private static readonly Dictionary<string, StoreMetadataEntry> _storeLevelCache = new Dictionary<string, StoreMetadataEntry>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// The list maintains the store metadata entries that are still in use, maybe because someone is still holding a strong reference
        /// to it. We need to scan this list everytime the clean up thread wakes up and make sure if the item collection is no longer in use,
        /// call clear on query cache
        /// </summary>
        private static readonly List<StoreMetadataEntry> _metadataEntriesRemovedFromCache = new List<StoreMetadataEntry>();

        private static Memoizer<string, List<MetadataArtifactLoader>> _artifactLoaderCache = new Memoizer<string, List<MetadataArtifactLoader>>(MetadataCache.SplitPaths, null);

        /// <summary>
        /// Read/Write lock for edm cache
        /// </summary>
        private static readonly object _edmLevelLock = new object();

        /// <summary>
        /// Read/Write lock for the store cache
        /// </summary>
        private static readonly object _storeLevelLock = new object();

        // Periodic thread which runs every n mins (look up the cleanupPeriod variable to see the exact time), walks through
        // every item in other store and edm cache and tries to do some cleanup
        private static Timer timer = new Timer(PeriodicCleanupCallback, null, cleanupPeriod, cleanupPeriod);

        #endregion

        #region Methods

        /// <summary>
        /// The purpose of the thread is to do cleanup. It marks the object in various stages before it actually cleans up the object
        /// Here's what this does for each entry in the cache:
        ///     1> First checks if the entry is marked for cleanup.
        ///     2> If the entry is marked for cleanup, that means its in one of the following 3 states
        ///         a) If the strong reference to item collection is not null, it means that this item was marked for cleanup in 
        ///            the last cleanup cycle and we must make the strong reference set to null so that it can be garbage collected.
        ///         b) Otherwise, we are waiting for GC to collect the item collection so that we can remove this entry from the cache
        ///            If the weak reference to item collection is still alive, we don't do anything
        ///         c) If the weak reference to item collection is not alive, we need to remove this entry from the cache
        ///     3> If the entry is not marked for cleanup, then check whether the weak reference to entry token is alive
        ///         a) if it is alive, then this entry is in use and we must do nothing
        ///         b) Otherwise, we can mark this entry for cleanup
        /// </summary>
        /// <param name="state"></param>
        private static void PeriodicCleanupCallback(object state)
        {
            // Perform clean up on edm cache
            DoCacheClean<EdmMetadataEntry>(_edmLevelCache, _edmLevelLock);

            // Perform clean up on store cache
            DoCacheClean<StoreMetadataEntry>(_storeLevelCache, _storeLevelLock);
        }

        /// <summary>
        /// A helper function for splitting up a string that is a concatenation of strings delimited by the metadata
        /// path separator into a string list. The resulting list is NOT sorted.
        /// </summary>
        /// <param name="paths">The paths to split</param>
        /// <returns>An array of strings</returns>
        [ResourceExposure(ResourceScope.Machine)] //Exposes the file name which is a Machine resource
        [ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.Create method call. But the path is not created in this method.
        internal static List<MetadataArtifactLoader> SplitPaths(string paths)
        {
            Debug.Assert(!string.IsNullOrEmpty(paths), "paths cannot be empty or null");
            
            string[] results;

            // This is the registry of all URIs in the global collection.
            HashSet<string> uriRegistry = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();

            // If the argument contains one or more occurrences of the macro '|DataDirectory|', we
            // pull those paths out so that we don't lose them in the string-splitting logic below.
            // Note that the macro '|DataDirectory|' cannot have any whitespace between the pipe 
            // symbols and the macro name. Also note that the macro must appear at the beginning of 
            // a path (else we will eventually fail with an invalid path exception, because in that
            // case the macro is not expanded). If a real/physical folder named 'DataDirectory' needs
            // to be included in the metadata path, whitespace should be used on either or both sides
            // of the name.
            //
            List<string> dataDirPaths = new List<string>();

            int indexStart = paths.IndexOf(MetadataCache.s_dataDirectory, StringComparison.OrdinalIgnoreCase);
            while (indexStart != -1)
            {
                int prevSeparatorIndex = indexStart == 0 ? -1 : paths.LastIndexOf(
                                                                MetadataCache.s_metadataPathSeparator,
                                                                indexStart - 1, // start looking here
                                                                StringComparison.Ordinal
                                                            );

                int macroPathBeginIndex = prevSeparatorIndex + 1;

                // The '|DataDirectory|' macro is composable, so identify the complete path, like
                // '|DataDirectory|\item1\item2'. If the macro appears anywhere other than at the
                // beginning, splice out the entire path, e.g. 'C:\item1\|DataDirectory|\item2'. In this
                // latter case the macro will not be expanded, and downstream code will throw an exception.
                //
                int indexEnd = paths.IndexOf(MetadataCache.s_metadataPathSeparator,
                                             indexStart + MetadataCache.s_dataDirectory.Length,
                                             StringComparison.Ordinal);
                if (indexEnd == -1)
                {
                    dataDirPaths.Add(paths.Substring(macroPathBeginIndex));
                    paths = paths.Remove(macroPathBeginIndex);   // update the concatenated list of paths
                    break;
                }

                dataDirPaths.Add(paths.Substring(macroPathBeginIndex, indexEnd - macroPathBeginIndex));

                // Update the concatenated list of paths by removing the one containing the macro.
                //
                paths = paths.Remove(macroPathBeginIndex, indexEnd - macroPathBeginIndex);
                indexStart = paths.IndexOf(MetadataCache.s_dataDirectory, StringComparison.OrdinalIgnoreCase);
            }

            // Split the string on the separator and remove all spaces around each parameter value
            results = paths.Split(new string[] { MetadataCache.s_metadataPathSeparator }, StringSplitOptions.RemoveEmptyEntries);

            // Now that the non-macro paths have been identified, merge the paths containing the macro
            // into the complete list.
            //
            if (dataDirPaths.Count > 0)
            {
                dataDirPaths.AddRange(results);
                results = dataDirPaths.ToArray();
            }

            for (int i = 0; i < results.Length; i++)
            {
                // Trim out all the spaces for this parameter and add it only if it's not blank
                results[i] = results[i].Trim();
                if (results[i].Length > 0)
                {
                    loaders.Add(MetadataArtifactLoader.Create(
                                    results[i],
                                    MetadataArtifactLoader.ExtensionCheck.All,  // validate the extension against all acceptable values
                                    null,
                                    uriRegistry
                                ));
                }
            }

            return loaders;
        }


        /// <summary>
        /// Walks through the given cache and calls cleanup on each entry in the cache
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache"></param>
        /// <param name="objectToLock"></param>
        private static void DoCacheClean<T>(Dictionary<string, T> cache, object objectToLock) where T: MetadataEntry
        {
            // Sometime, for some reason, timer can be initialized and the cache is still not initialized.
            if (cache != null)
            {
                List<KeyValuePair<string, T>> keysForRemoval = null;

                lock (objectToLock)
                {
                    // we should check for type of the lock object first, since otherwise we might be reading the count of the list
                    // while some other thread might be modifying it. For e.g. when this function is called for edmcache,
                    // we will be acquiring edmlock and trying to get the count for the list, while some other thread
                    // might be calling ClearCache and we might be adding entries to the list
                    if (objectToLock == _storeLevelLock && _metadataEntriesRemovedFromCache.Count != 0)
                    {
                        // First check the list of entries and remove things which are no longer in use
                        for (int i = _metadataEntriesRemovedFromCache.Count - 1; 0 <= i; i--)
                        {
                            if (!_metadataEntriesRemovedFromCache[i].IsEntryStillValid())
                            {
                                // Clear the query cache
                                _metadataEntriesRemovedFromCache[i].CleanupQueryCache();
                                // Remove the entry at the current index. This is the reason why we
                                // go backwards.
                                _metadataEntriesRemovedFromCache.RemoveAt(i);
                            }
                        }
                    }

                    // We have to use a list to keep track of the keys to remove because we can't remove while enumerating
                    foreach (KeyValuePair<string, T> pair in cache)
                    {
                        if (pair.Value.PeriodicCleanUpThread())
                        {
                            if (keysForRemoval == null)
                            {
                                keysForRemoval = new List<KeyValuePair<string, T>>();
                            }
                            keysForRemoval.Add(pair);
                        }
                    }

                    // Remove all the entries from the cache
                    if (keysForRemoval != null)
                    {
                        for (int i = 0; i < keysForRemoval.Count; i++)
                        {
                            keysForRemoval[i].Value.Clear();
                            cache.Remove(keysForRemoval[i].Key);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves an cache entry holding to edm metadata for a given cache key
        /// </summary>
        /// <param name="cacheKey">string containing all the files from which edm metadata is to be retrieved</param>
        /// <param name="composite">An instance of the composite MetadataArtifactLoader</param>
        /// <param name="entryToken">The metadata entry token for the returned entry</param>
        /// <returns>Returns the entry containing the edm metadata</returns>
        internal static EdmItemCollection GetOrCreateEdmItemCollection(string cacheKey, 
                                                             MetadataArtifactLoader loader,
                                                             out object entryToken)
        {
            EdmMetadataEntry entry = GetCacheEntry<EdmMetadataEntry>(_edmLevelCache, cacheKey, _edmLevelLock,
                new EdmMetadataEntryConstructor(), out entryToken);

            // Load the edm item collection or if the collection is already loaded, check for security permission
            LoadItemCollection(new EdmItemCollectionLoader(loader), entry);

            return entry.EdmItemCollection;
        }

        /// <summary>
        /// Retrieves an entry holding store metadata for a given cache key
        /// </summary>
        /// <param name="cacheKey">The connection string whose store metadata is to be retrieved</param>
        /// <param name="composite">An instance of the composite MetadataArtifactLoader</param>
        /// <param name="entryToken">The metadata entry token for the returned entry</param>
        /// <returns>the entry containing the information on how to load store metadata</returns>
        internal static StorageMappingItemCollection GetOrCreateStoreAndMappingItemCollections(
                                                                 string cacheKey,
                                                                 MetadataArtifactLoader loader,
                                                                 EdmItemCollection edmItemCollection,
                                                                 out object entryToken)
        {
            StoreMetadataEntry entry = GetCacheEntry<StoreMetadataEntry>(_storeLevelCache, cacheKey, _storeLevelLock,
                new StoreMetadataEntryConstructor(), out entryToken);

            // Load the store item collection or if the collection is already loaded, check for security permission
            LoadItemCollection(new StoreItemCollectionLoader(edmItemCollection, loader), entry);

            return entry.StorageMappingItemCollection;
        }

        internal static List<MetadataArtifactLoader> GetOrCreateMetdataArtifactLoader(string paths)
        {
            return _artifactLoaderCache.Evaluate(paths);
        }

        /// <summary>
        /// Get the entry from the cache given the cache key. If the entry is not present, it creates a new entry and
        /// adds it to the cache
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="cache"></param>
        /// <param name="cacheKey"></param>
        /// <param name="entryToken"></param>
        /// <param name="metadataEntry"></param>
        /// <param name="objectToLock"></param>
        /// <returns></returns>
        private static T GetCacheEntry<T>(Dictionary<string, T> cache, string cacheKey, object objectToLock, 
            IMetadataEntryConstructor<T> metadataEntry, out object entryToken) where T: MetadataEntry
        {
            T entry;

            // In the critical section, we need to do the minimal thing to ensure correctness
            // Within the lock, we will see if an entry is present. If it is not, we will create a new entry and
            // add it to the cache. In either case, we need to ensure the token to make sure so that any other
            // thread that comes looking for the same entry does nothing in this critical section
            // Also the cleanup thread doesn't do anything since the token is alive
            lock (objectToLock)
            {
                if (cache.TryGetValue(cacheKey, out entry))
                {
                    entryToken = entry.EnsureToken();
                }
                else
                {
                    entry = metadataEntry.GetMetadataEntry();
                    entryToken = entry.EnsureToken();
                    cache.Add(cacheKey, entry);
                }
            }

            return entry;
        }

        /// <summary>
        /// Loads the item collection for the entry
        /// </summary>
        /// <param name="itemCollectionLoader">struct which loads an item collection</param>
        /// <param name="entry">entry whose item collection needs to be loaded</param>
        private static void LoadItemCollection<T>(IItemCollectionLoader<T> itemCollectionLoader, T entry) where T : MetadataEntry
        {
            // At this point, you have made sure that there is an entry with an alive token in the cache so that
            // other threads can find it if they come querying for it, and cleanup thread won't clean the entry
            // If two or more threads come one after the other, we don't won't both of them to load the metadata.
            // So if one of them is loading the metadata, the other should wait and then use the same metadata.
            // For that reason, we have this lock on the entry itself to make sure that this happens. Its okay to
            // update the item collection outside the lock, since assignment are guarantees to be atomic and no two
            // thread are updating this at the same time
            bool isItemCollectionAlreadyLoaded = true;

            if (!entry.IsLoaded)
            {
                lock (entry)
                {
                    if (!entry.IsLoaded)
                    {
                        itemCollectionLoader.LoadItemCollection(entry);
                        isItemCollectionAlreadyLoaded = false;
                    }
                }
            }

            Debug.Assert(entry.IsLoaded, "The entry must be loaded at this point");

            // Making sure that the thread which loaded the item collection is not checking for file permisssions
            // again
            if (isItemCollectionAlreadyLoaded)
            {
                entry.CheckFilePermission();
            }
        }
                
        /// <summary>
        /// Remove all the entries from the cache
        /// </summary>
        internal static void Clear()
        {
            lock (_edmLevelLock)
            {
                _edmLevelCache.Clear();
            }

            lock (_storeLevelLock)
            {
                // Call clear on each of the metadata entries. This is to make sure we clear all the performance
                // counters associated with the query cache
                foreach (StoreMetadataEntry entry in _storeLevelCache.Values)
                {
                    // Check if the weak reference to item collection is still alive
                    if (entry.IsEntryStillValid())
                    {
                        _metadataEntriesRemovedFromCache.Add(entry);
                    }
                    else
                    {
                        entry.Clear();
                    }
                }
                _storeLevelCache.Clear();
            }

            Memoizer<string, List<MetadataArtifactLoader>> artifactLoaderCacheTemp =
                new Memoizer<string, List<MetadataArtifactLoader>>(MetadataCache.SplitPaths, null);

            Interlocked.CompareExchange(ref _artifactLoaderCache, artifactLoaderCacheTemp, _artifactLoaderCache);
        }

        #endregion

        #region InlineClasses

        /// <summary>
        /// The base class having common implementation for all metadata entry classes
        /// </summary>
        private abstract class MetadataEntry
        {
            private WeakReference _entryTokenReference;
            private ItemCollection _itemCollection;
            private WeakReference _weakReferenceItemCollection;
            private bool _markEntryForCleanup;
            private FileIOPermission _filePermissions;

            /// <summary>
            /// The constructor for constructing this MetadataEntry
            /// </summary>
            internal MetadataEntry()
            {
                // Create this once per life time of the object. Creating extra weak references causing unnecessary GC pressure
                _entryTokenReference = new WeakReference(null);
                _weakReferenceItemCollection = new WeakReference(null);
            }

            /// <summary>
            /// returns the item collection inside this metadata entry
            /// </summary>
            protected ItemCollection ItemCollection { get { return _itemCollection; } }

            /// <summary>
            /// Update the entry with the given item collection
            /// </summary>
            /// <param name="itemCollection"></param>
            protected void UpdateMetadataEntry(ItemCollection itemCollection, FileIOPermission filePermissions)
            {
                Debug.Assert(_entryTokenReference.IsAlive, "You must call Ensure token before you call this method");
                Debug.Assert(_markEntryForCleanup == false, "The entry must not be marked for cleanup");
                Debug.Assert(_itemCollection == null, "Item collection must be null");
                Debug.Assert(_filePermissions == null, "filePermissions must be null");

                // Update strong and weak reference for item collection
                _weakReferenceItemCollection.Target = itemCollection;
                _filePermissions = filePermissions;
                
                // do this last, because it signals that we are loaded
                _itemCollection = itemCollection;
            }

            internal bool IsLoaded { get { return _itemCollection != null; } }

            /// <summary>
            /// This method is called periodically by the cleanup thread to make the unused entries
            /// go through various stages, before it is ready for cleanup. If it is ready, this method
            /// returns true and then the entry is completely removed from the cache
            /// </summary>
            /// <returns></returns>
            internal bool PeriodicCleanUpThread()
            {
                // Here's what this does for each entry in the cache:
                //     1> First checks if the entry is marked for cleanup.
                //     2> If the entry is marked for cleanup, that means its in one of the following 3 states
                //         a) If the strong reference to item collection is not null, it means that this item was marked for cleanup in 
                //            the last cleanup cycle and we must make the strong reference set to null so that it can be garbage collected. (GEN 2)
                //         b) Otherwise, we are waiting for GC to collect the item collection so that we can remove this entry from the cache
                //            If the weak reference to item collection is still alive, we don't do anything
                //         c) If the weak reference to item collection is not alive, we need to remove this entry from the cache (GEN 3)
                //     3> If the entry is not marked for cleanup, then check whether the weak reference to entry token is alive
                //         a) if it is alive, then this entry is in use and we must do nothing
                //         b) Otherwise, we can mark this entry for cleanup (GEN 1)
                if (_markEntryForCleanup)
                {
                    Debug.Assert(_entryTokenReference.IsAlive == false, "Entry Token must never be alive if the entry is marked for cleanup");

                    if (_itemCollection != null)
                    {
                        // GEN 2
                        _itemCollection = null;
                    }
                    else if (!_weakReferenceItemCollection.IsAlive)
                    {
                        // GEN 3
                        _filePermissions = null;
                        // this entry must be removed from the cache
                        return true;
                    }
                }
                else if (!_entryTokenReference.IsAlive)
                {
                    // GEN 1

                    // If someone creates a entity connection, and calls GetMetadataWorkspace. This creates an cache entry,
                    // but the item collection is not initialized yet (since store item collection are initialized only 
                    // when one calls connection.Open()). Suppose now the connection is no longer used - in other words,
                    // open was never called and it goes out of scope. After some time when the connection gets GC'ed,
                    // entry token won't be alive any longer, but item collection inside it will be null, since it was never initialized.
                    // So we can't assert that item collection must be always initialized here
                    _markEntryForCleanup = true;
                }

                return false;
            }

            /// <summary>
            /// Make sure that the entry has a alive token and returns that token - it can be new token or an existing
            /// one, depending on the state of the entry
            /// </summary>
            /// <returns></returns>
            internal object EnsureToken()
            {
                object entryToken = _entryTokenReference.Target;
                ItemCollection itemCollection = (ItemCollection)_weakReferenceItemCollection.Target;

                // When ensure token is called, the entry can be in different stages
                // 1> Its a newly created entry - no token, no item collection, etc. Just create a new token and 
                //    return back
                // 2> An entry already in use - the weak reference to token must be alive. We just need to grab the token
                //    and return it
                // 3> No one is using this entry and hence the token is no longer alive. If we have strong reference to item
                //    collection, then create a new token and return it
                // 4> No one has used this token for one cleanup cycle and hence strong reference is null. But the weak reference
                //    is still alive. We need to make the initialize the strong reference again, create a new token and return it
                // 5> This entry has not been used for long enough that even the weak reference is no longer alive. This entry is
                //    now exactly like a new entry, except that it is still marked for cleanup. Create a new token, set mark for
                //    cleanup to false and return the token
                if (_entryTokenReference.IsAlive)
                {
                    Debug.Assert(_markEntryForCleanup == false, "An entry with alive token cannot be marked for cleanup");
                    // ItemCollection strong pointer can be null or not null. If the entry has been created, and loadItemCollection
                    // hasn't been called yet, the token will be alive, but item collection will be null. If someone called
                    // load item collection, then item collection will not be non-null
                    return entryToken;
                }
                // If the entry token is not alive, then it can be either a new created entry with everything set
                // to null or it must be one of the entries which is no longer in use
                else if (_itemCollection != null)
                {
                    Debug.Assert(_weakReferenceItemCollection.IsAlive, "Since the strong reference is still there, weak reference must also be alive");
                    // This means that no one is using the item collection, and its waiting to be cleanuped
                }
                else 
                {
                    if (_weakReferenceItemCollection.IsAlive)
                    {
                        Debug.Assert(_markEntryForCleanup, "Since the strong reference is null, this entry must be marked for cleanup");
                        // Initialize the strong reference to item collection
                        _itemCollection = itemCollection;
                    }
                    else
                    {
                        // no more references to the collection
                        // are available, so get rid of the permissions
                        // object.  We will get a new one when we get a new collection
                        _filePermissions = null;
                    }
                }
                // Even if the _weakReferenceItemCollection is no longer alive, we will reuse this entry. Assign a new entry token and set mark for cleanup to false
                // so that this entry is not cleared by the cleanup thread

                entryToken = new object();
                _entryTokenReference.Target = entryToken;
                _markEntryForCleanup = false;
                return entryToken;
            }

            /// <summary>
            /// Check if the thread has appropriate permissions to use the already loaded metadata
            /// </summary>
            internal void CheckFilePermission()
            {
                Debug.Assert(_itemCollection != null, "Item collection must be present since we want to reuse the metadata");
                Debug.Assert(_entryTokenReference.IsAlive, "This entry must be in use");
                Debug.Assert(_markEntryForCleanup == false, "The entry must not marked for cleanup");
                Debug.Assert(_weakReferenceItemCollection.IsAlive, "Weak reference to item collection must be alive");

                // we will have an empty ItemCollection (no files were used to load it)
                if (_filePermissions != null)
                {
                    _filePermissions.Demand();
                }
            }

            /// <summary>
            /// Dispose the composite loader that encapsulates all artifacts
            /// </summary>
            internal virtual void Clear()
            {
            }

            /// <summary>
            /// This returns true if the entry is still in use - the entry can be use if the entry token is 
            /// still alive.If the entry token is still not alive, it means that no one is using this entry
            /// and its okay to remove it. Today there is no
            /// </summary>
            /// <returns></returns>
            internal bool IsEntryStillValid()
            {
                return _entryTokenReference.IsAlive;
            }
        }

        /// <summary>
        /// A metadata entry holding EdmItemCollection object for the cache
        /// </summary>
        private class EdmMetadataEntry : MetadataEntry
        {
            /// <summary>
            /// Gets the EdmItemCollection for this entry
            /// </summary>
            internal EdmItemCollection EdmItemCollection
            {
                get
                {
                    return (EdmItemCollection)this.ItemCollection;
                }
            }

            /// <summary>
            /// Just loads the edm item collection
            /// </summary>
            /// <returns></returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
            internal void LoadEdmItemCollection(MetadataArtifactLoader loader)
            {
                Debug.Assert(loader != null, "loader is null");

                List<XmlReader> readers = loader.CreateReaders(DataSpace.CSpace);
                try
                {
                    EdmItemCollection itemCollection = new EdmItemCollection(
                                                           readers,
                                                           loader.GetPaths(DataSpace.CSpace)
                                                            );

                    List<string> permissionPaths = new List<string>();
                    loader.CollectFilePermissionPaths(permissionPaths, DataSpace.CSpace);
                    FileIOPermission filePermissions = null;
                    if (permissionPaths.Count > 0)
                    {
                        filePermissions = new FileIOPermission(FileIOPermissionAccess.Read, permissionPaths.ToArray());
                    }

                    UpdateMetadataEntry(itemCollection, filePermissions);
                }
                finally
                {
                    Helper.DisposeXmlReaders(readers);
                }
            }
        }

        /// <summary>
        /// A metadata entry holding a StoreItemCollection and a StorageMappingItemCollection objects for the cache
        /// </summary>
        private class StoreMetadataEntry : MetadataEntry
        {
            private System.Data.Common.QueryCache.QueryCacheManager _queryCacheManager;

            /// <summary>
            /// The constructor for constructing this entry with an StoreItemCollection and a StorageMappingItemCollection
            /// </summary>
            /// <param name="compositeLoader">An instance of the composite MetadataArtifactLoader</param>
            internal StoreMetadataEntry()
            {
            }

            /// <summary>
            /// Gets the StorageMappingItemCollection for this entry
            /// </summary>
            internal StorageMappingItemCollection StorageMappingItemCollection
            {
                get
                {
                    return (StorageMappingItemCollection)this.ItemCollection;
                }
            }

            /// <summary>
            /// Load store specific metadata into the StoreItemCollection for this entry
            /// </summary>
            /// <param name="factory">The store-specific provider factory</param>
            /// <param name="edmItemCollection">edmItemCollection</param>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
            internal void LoadStoreCollection(EdmItemCollection edmItemCollection, MetadataArtifactLoader loader)
            {
                StoreItemCollection storeItemCollection = null;
                IEnumerable<XmlReader> sSpaceXmlReaders = loader.CreateReaders(DataSpace.SSpace);
                try
                {
                    // Load the store side, however, only do so if we don't already have one
                    storeItemCollection = new StoreItemCollection(
                                    sSpaceXmlReaders,
                                    loader.GetPaths(DataSpace.SSpace));

                }
                finally
                {
                    Helper.DisposeXmlReaders(sSpaceXmlReaders);
                }

                // If this entry is getting re-used, make sure that the previous query cache manager gets
                // cleared up
                if (_queryCacheManager != null)
                {
                    _queryCacheManager.Clear();
                }

                // Update the query cache manager reference
                _queryCacheManager = storeItemCollection.QueryCacheManager;

                // With the store metadata in place, we can then load the mappings, however, only use it 
                // if we don't already have one
                //
                StorageMappingItemCollection storageMappingItemCollection = null;
                IEnumerable<XmlReader> csSpaceXmlReaders = loader.CreateReaders(DataSpace.CSSpace);
                try
                {
                    storageMappingItemCollection = new StorageMappingItemCollection(
                                                                        edmItemCollection,
                                                                        storeItemCollection,
                                                                        csSpaceXmlReaders,
                                                                        loader.GetPaths(DataSpace.CSSpace));
                }
                finally
                {
                    Helper.DisposeXmlReaders(csSpaceXmlReaders);
                }

                List<string> permissionPaths = new List<string>();
                loader.CollectFilePermissionPaths(permissionPaths, DataSpace.SSpace);
                loader.CollectFilePermissionPaths(permissionPaths, DataSpace.CSSpace);
                FileIOPermission filePermissions = null;
                if (permissionPaths.Count > 0)
                {
                    filePermissions = new FileIOPermission(FileIOPermissionAccess.Read, permissionPaths.ToArray());
                }
                this.UpdateMetadataEntry(storageMappingItemCollection, filePermissions);

            }

            /// <summary>
            /// Calls clear on query cache manager to make sure all the performance counters associated with the query
            /// cache are gone
            /// </summary>
            internal override void Clear()
            {
                // there can be entries in cache for which the store item collection was never created. For e.g.
                // if you create a new entity connection, but never call open on it
                CleanupQueryCache();
                base.Clear();
            }

            /// <summary>
            /// Cleans and Dispose query cache manager
            /// </summary>
            internal void CleanupQueryCache()
            {
                if (null != _queryCacheManager)
                {
                    _queryCacheManager.Dispose();
                    _queryCacheManager = null;
                }
            }

        }

        /// <summary>
        /// Interface to construct the metadata entry so that code can be reused
        /// </summary>
        /// <typeparam name="T"></typeparam>
        interface IMetadataEntryConstructor<T>
        {
            T GetMetadataEntry();
        }

        /// <summary>
        /// Struct for creating EdmMetadataEntry
        /// </summary>
        private struct EdmMetadataEntryConstructor : IMetadataEntryConstructor<EdmMetadataEntry>
        {
            public EdmMetadataEntry GetMetadataEntry()
            {
                return new EdmMetadataEntry();
            }
        }

        /// <summary>
        /// Struct for creating StoreMetadataEntry
        /// </summary>
        private struct StoreMetadataEntryConstructor : IMetadataEntryConstructor<StoreMetadataEntry>
        {
            public StoreMetadataEntry GetMetadataEntry()
            {
                return new StoreMetadataEntry();
            }
        }

        /// <summary>
        /// Interface which constructs a new Item collection
        /// </summary>
        /// <typeparam name="T"></typeparam>
        interface IItemCollectionLoader<T> where T : MetadataEntry
        {
            void LoadItemCollection(T entry);
        }

        private struct EdmItemCollectionLoader : IItemCollectionLoader<EdmMetadataEntry>
        {

            private MetadataArtifactLoader _loader;

            public EdmItemCollectionLoader(MetadataArtifactLoader loader)
            {
                Debug.Assert(loader != null, "loader must never be null");
                _loader = loader;
            }
            
            /// <summary>
            /// Creates a new item collection and updates the entry with the item collection
            /// </summary>
            /// <param name="entry"></param>
            /// <returns></returns>
            public void LoadItemCollection(EdmMetadataEntry entry)
            {
                entry.LoadEdmItemCollection(_loader);
            }
        }

        private struct StoreItemCollectionLoader : IItemCollectionLoader<StoreMetadataEntry>
        {
            private EdmItemCollection _edmItemCollection;
            private MetadataArtifactLoader _loader;

            /// <summary>
            /// Constructs a struct from which you can load edm item collection
            /// </summary>
            /// <param name="factory"></param>
            /// <param name="edmItemCollection"></param>
            internal StoreItemCollectionLoader(EdmItemCollection edmItemCollection, MetadataArtifactLoader loader)
            {
                Debug.Assert(edmItemCollection != null, "EdmItemCollection must never be null");
                Debug.Assert(loader != null, "loader must never be null");
                //StoreItemCollection requires atleast one SSDL path.
                if ((loader.GetPaths(DataSpace.SSpace) == null) || (loader.GetPaths(DataSpace.SSpace).Count == 0))
                {
                    throw EntityUtil.Metadata(Strings.AtleastOneSSDLNeeded);
                }

                _edmItemCollection = edmItemCollection;
                _loader = loader;
            }

            public void LoadItemCollection(StoreMetadataEntry entry)
            {
                entry.LoadStoreCollection(_edmItemCollection, _loader);
            }
        }

        #endregion
    }
}