[mapguide-commits] r6545 - in trunk/Tools/Maestro: Maestro.Editors/FeatureSource Maestro.Editors/FeatureSource/Extensions MaestroAPITests OSGeo.MapGuide.MaestroAPI OSGeo.MapGuide.MaestroAPI/Schema OSGeo.MapGuide.MaestroAPI/Services OSGeo.MapGuide.MaestroAPI.Http OSGeo.MapGuide.MaestroAPI.Local OSGeo.MapGuide.MaestroAPI.Native

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Tue Mar 13 11:15:03 EDT 2012


Author: jng
Date: 2012-03-13 08:15:03 -0700 (Tue, 13 Mar 2012)
New Revision: 6545

Modified:
   trunk/Tools/Maestro/Maestro.Editors/FeatureSource/Extensions/ExtendedClassSettings.cs
   trunk/Tools/Maestro/Maestro.Editors/FeatureSource/ExtensionsCtrl.cs
   trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs
   trunk/Tools/Maestro/MaestroAPITests/LocalNativeFeatureTests.cs
   trunk/Tools/Maestro/MaestroAPITests/TestControl.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/HttpServerConnection.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/RequestBuilder.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Local/LocalConnection.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/LocalNativeConnection.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/Utility.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/PlatformConnectionBase.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/ClassDefinition.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSchema.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSourceDescription.cs
   trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Services/IFeatureService.cs
Log:
#1957: Fix timeout on saving feature sources with really large schemas. The problem is due to:
 1. The New Extended Feature Class UI, needing a list of class definitions when it actually just needs a list of qualified class names.
 2. The Feature Source Extension UI was caching a full schema for the purpose of passing it to the New Extended Feature Class UI, and to do Class Definition retrieval.
 3. It was always doing a full schema request on successful save
 4. The implementation of IFeatureService.GetClassDefinition does not actually map to the underlying GETCLASSDEFINITION mapagent call, or the MgFeatureService.GetClassDefinition API, it queries and locally caches the full schema and finds the matching class definition from that.

For a large schema, all 4 problems go through the same bottleneck (needing to do a full schema walk) when we shouldn't have to. This submission does the following:

 1. Restructures the APIs for PlatformConnectionBase and its subclasses to support fetching a single Class Definition without needing to do a full schema walk.
 2. Modifies the New Extended Feature Class UI so that only a only a list of qualified class names is passed to it instead of a list of Class Definitions
 3. Modifies the Feature Source Extension UI to only fetch a list of class names instead of doing a full schema walk (as that is all the New Extended Feature Class UI requires). This allows MapGuide to take advantage of FDO RFC23 enhancements server-side where applicable.
 4. Restructure the internal object caching mechanisms in PlatformConnectionBase to ensure that:
   - Class Definitions returned by GetClassDefinition calls can be cached internally for future calls
   - A successful save of a resource purges all associated feature source cache items for that resource

New unit tests are added for HTTP, Local and LocalNative connections to exercise the caching mechanisms.

This submission also fixes some defects with cloning and conversion of ClassDefinition objects.

As can be ascertained from this really long description of this submission, the changes involved weren't trivial, thus this change is unlikely to be backported to the 4.0.x branch if anyone cares to ask.

Modified: trunk/Tools/Maestro/Maestro.Editors/FeatureSource/Extensions/ExtendedClassSettings.cs
===================================================================
--- trunk/Tools/Maestro/Maestro.Editors/FeatureSource/Extensions/ExtendedClassSettings.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/Maestro.Editors/FeatureSource/Extensions/ExtendedClassSettings.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -39,21 +39,19 @@
             InitializeComponent();
         }
 
-        public ExtendedClassSettings(IEnumerable<ClassDefinition> classes, IFeatureSourceExtension ext)
+        public ExtendedClassSettings(IEnumerable<string> qualifiedClassNames, IFeatureSourceExtension ext)
             : this()
         {
-            var klasses = new List<ClassDefinition>(classes);
-            cmbBaseClass.DisplayMember = "QualifiedName";
-            cmbBaseClass.ValueMember = "QualifiedName";
-            cmbBaseClass.DataSource = klasses;
+            var names = new List<string>(qualifiedClassNames);
+            cmbBaseClass.DataSource = names;
             ext.PropertyChanged += (sender, e) => { OnResourceChanged(); };
 
             //HACK
             if (string.IsNullOrEmpty(ext.FeatureClass))
-                ext.FeatureClass = klasses[0].QualifiedName;
+                ext.FeatureClass = names[0];
             
             TextBoxBinder.BindText(txtExtendedName, ext, "Name");
-            ComboBoxBinder.BindSelectedIndexChanged(cmbBaseClass, "SelectedValue", ext, "FeatureClass");
+            ComboBoxBinder.BindSelectedIndexChanged(cmbBaseClass, "SelectedItem", ext, "FeatureClass");
         }
 
         //private void txtExtendedName_TextChanged(object sender, EventArgs e)

Modified: trunk/Tools/Maestro/Maestro.Editors/FeatureSource/ExtensionsCtrl.cs
===================================================================
--- trunk/Tools/Maestro/Maestro.Editors/FeatureSource/ExtensionsCtrl.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/Maestro.Editors/FeatureSource/ExtensionsCtrl.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -48,8 +48,6 @@
         const int IDX_JOIN = 2;
 
         private IFeatureSource _fs;
-        private FeatureSourceDescription _cachedSchema;
-
         private IEditorService _edSvc;
 
         public override void Bind(IEditorService service)
@@ -146,21 +144,8 @@
         void OnResourceSaved(object sender, EventArgs e)
         {
             Debug.Assert(!_edSvc.IsNew);
-            _cachedSchema = _fs.Describe();
         }
 
-        private FeatureSourceDescription CachedSchema()
-        {
-            if (_cachedSchema == null)
-            {
-                using (new WaitCursor(this))
-                {
-                    _cachedSchema = _fs.Describe();
-                }
-            }
-            return _cachedSchema;
-        }
-
         private void btnNewExtension_Click(object sender, EventArgs e)
         {
             if (_edSvc.IsNew)
@@ -327,7 +312,7 @@
             var calc = e.Node.Tag as ICalculatedProperty;
             if (ext != null)
             {
-                var ctl = new ExtendedClassSettings(CachedSchema().AllClasses, ext);
+                var ctl = new ExtendedClassSettings(GetAllClassNames(), ext);
                 ctl.Dock = DockStyle.Fill;
                 //If editing to something valid, update the toolbar
                 ctl.ResourceChanged += (s, evt) =>
@@ -346,7 +331,7 @@
                 ext = e.Node.Parent.Tag as IFeatureSourceExtension;
                 if (ext != null)
                 {
-                    ClassDefinition cls = CachedSchema().GetClass(ext.FeatureClass);
+                    ClassDefinition cls = _fs.GetClass(ext.FeatureClass); //TODO: Cache?
                     
                     if (cls != null)
                     {
@@ -364,7 +349,7 @@
                 ext = e.Node.Parent.Tag as IFeatureSourceExtension;
                 if (ext != null)
                 {
-                    ClassDefinition cls = CachedSchema().GetClass(ext.FeatureClass);
+                    ClassDefinition cls = _fs.GetClass(ext.FeatureClass); //TODO: Cache?
                     if (cls != null)
                     {
                         var ctl = new CalculationSettings(_edSvc, cls, _fs, calc);
@@ -380,5 +365,15 @@
                 splitContainer1.Panel2.Controls.Clear();
             }
         }
+
+        string[] GetAllClassNames()
+        {
+            var names = new List<string>();
+            foreach (var sn in _fs.GetSchemaNames())
+            {
+                names.AddRange(_fs.GetClassNames(sn));
+            }
+            return names.ToArray();
+        }
     }
 }

Modified: trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs
===================================================================
--- trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/MaestroAPITests/HttpConnectionTests.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -23,8 +23,10 @@
 using NUnit.Framework;
 using OSGeo.MapGuide.MaestroAPI;
 using OSGeo.MapGuide.MaestroAPI.Services;
+using OSGeo.MapGuide.MaestroAPI.Resource;
 using System.IO;
 using OSGeo.MapGuide.ObjectModels.Common;
+using OSGeo.MapGuide.ObjectModels;
 
 namespace MaestroAPITests
 {
@@ -32,6 +34,78 @@
     public class HttpConnectionTests
     {
         [Test]
+        public void TestFeatureSourceCaching()
+        {
+            var conn = ConnectionUtil.CreateTestHttpConnection();
+            string fsId = "Library://UnitTests/HttpCaching.FeatureSource";
+            if (!conn.ResourceService.ResourceExists(fsId))
+            {
+                var fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SDF");
+                fs.SetConnectionProperty("File", "%MG_DATA_FILE_PATH%Sheboygan_Parcels.sdf");
+                fs.ResourceID = fsId;
+                conn.ResourceService.SaveResourceAs(fs, fsId);
+                using (var stream = File.OpenRead("TestData/FeatureService/SDF/Sheboygan_Parcels.sdf"))
+                {
+                    fs.SetResourceData("Sheboygan_Parcels.sdf", ResourceDataType.File, stream);
+                }
+                Assert.True(Convert.ToBoolean(conn.FeatureService.TestConnection(fsId)));
+            }
+            var pc = (PlatformConnectionBase)conn;
+            pc.ResetFeatureSourceSchemaCache();
+
+            Assert.AreEqual(0, pc.CachedClassDefinitions);
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+
+            var fsd = conn.FeatureService.DescribeFeatureSource(fsId);
+
+            Assert.AreEqual(1, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+
+            var fsd2 = conn.FeatureService.DescribeFeatureSource(fsId);
+
+            Assert.AreEqual(1, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+            //Each cached instance returned is a clone
+            Assert.False(object.ReferenceEquals(fsd, fsd2));
+        }
+
+        [Test]
+        public void TestClassDefinitionCaching()
+        {
+            var conn = ConnectionUtil.CreateTestHttpConnection();
+            string fsId = "Library://UnitTests/HttpCaching.FeatureSource";
+            if (!conn.ResourceService.ResourceExists(fsId))
+            {
+                var fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SDF");
+                fs.SetConnectionProperty("File", "%MG_DATA_FILE_PATH%Sheboygan_Parcels.sdf");
+                conn.ResourceService.SaveResourceAs(fs, fsId);
+                fs.ResourceID = fsId;
+                using (var stream = File.OpenRead("TestData/FeatureService/SDF/Sheboygan_Parcels.sdf"))
+                {
+                    fs.SetResourceData("Sheboygan_Parcels.sdf", ResourceDataType.File, stream);
+                }
+                Assert.True(Convert.ToBoolean(conn.FeatureService.TestConnection(fsId)));
+            }
+            var pc = (PlatformConnectionBase)conn;
+            pc.ResetFeatureSourceSchemaCache();
+
+            Assert.AreEqual(0, pc.CachedClassDefinitions);
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+
+            var cls = conn.FeatureService.GetClassDefinition(fsId, "SHP_Schema:Parcels");
+
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+
+            var cls2 = conn.FeatureService.GetClassDefinition(fsId, "SHP_Schema:Parcels");
+
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+            //Each cached instance returned is a clone
+            Assert.False(object.ReferenceEquals(cls, cls2));
+        }
+
+        [Test]
         public void TestSheboyganCsToPseudoMercator()
         {
             //Purpose: Unit test to guard against regression as a result of updating/replacing NTS

Modified: trunk/Tools/Maestro/MaestroAPITests/LocalNativeFeatureTests.cs
===================================================================
--- trunk/Tools/Maestro/MaestroAPITests/LocalNativeFeatureTests.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/MaestroAPITests/LocalNativeFeatureTests.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -22,11 +22,15 @@
 using System.Text;
 using NUnit.Framework;
 using OSGeo.MapGuide.MaestroAPI.Schema;
+using OSGeo.MapGuide.MaestroAPI.Resource;
 using OSGeo.MapGuide.MaestroAPI;
 using OSGeo.MapGuide.MaestroAPI.Feature;
 using OSGeo.MapGuide.MaestroAPI.Commands;
 using OSGeo.MapGuide.MaestroAPI.CoordinateSystem;
 using OSGeo.MapGuide.MaestroAPI.Internal;
+using OSGeo.MapGuide.ObjectModels;
+using OSGeo.MapGuide.ObjectModels.Common;
+using System.IO;
 
 namespace MaestroAPITests
 {
@@ -34,6 +38,76 @@
     {
         protected abstract IServerConnection CreateTestConnection();
 
+        protected void TestFeatureSourceCaching(string fsName)
+        {
+            var conn = ConnectionUtil.CreateTestHttpConnection();
+            string fsId = "Library://UnitTests/" + fsName + ".FeatureSource";
+            if (!conn.ResourceService.ResourceExists(fsId))
+            {
+                var fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SDF");
+                fs.SetConnectionProperty("File", "%MG_DATA_FILE_PATH%Sheboygan_Parcels.sdf");
+                fs.ResourceID = fsId;
+                conn.ResourceService.SaveResourceAs(fs, fsId);
+                using (var stream = File.OpenRead("TestData/FeatureService/SDF/Sheboygan_Parcels.sdf"))
+                {
+                    fs.SetResourceData("Sheboygan_Parcels.sdf", ResourceDataType.File, stream);
+                }
+                Assert.True(Convert.ToBoolean(conn.FeatureService.TestConnection(fsId)));
+            }
+            var pc = (PlatformConnectionBase)conn;
+            pc.ResetFeatureSourceSchemaCache();
+
+            Assert.AreEqual(0, pc.CachedClassDefinitions);
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+
+            var fsd = conn.FeatureService.DescribeFeatureSource(fsId);
+
+            Assert.AreEqual(1, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+
+            var fsd2 = conn.FeatureService.DescribeFeatureSource(fsId);
+
+            Assert.AreEqual(1, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+            //Each cached instance returned is a clone
+            Assert.False(object.ReferenceEquals(fsd, fsd2));
+        }
+
+        protected void TestClassDefinitionCaching(string fsName)
+        {
+            var conn = ConnectionUtil.CreateTestHttpConnection();
+            string fsId = "Library://UnitTests/" + fsName + ".FeatureSource";
+            if (!conn.ResourceService.ResourceExists(fsId))
+            {
+                var fs = ObjectFactory.CreateFeatureSource(conn, "OSGeo.SDF");
+                fs.SetConnectionProperty("File", "%MG_DATA_FILE_PATH%Sheboygan_Parcels.sdf");
+                conn.ResourceService.SaveResourceAs(fs, fsId);
+                fs.ResourceID = fsId;
+                using (var stream = File.OpenRead("TestData/FeatureService/SDF/Sheboygan_Parcels.sdf"))
+                {
+                    fs.SetResourceData("Sheboygan_Parcels.sdf", ResourceDataType.File, stream);
+                }
+                Assert.True(Convert.ToBoolean(conn.FeatureService.TestConnection(fsId)));
+            }
+            var pc = (PlatformConnectionBase)conn;
+            pc.ResetFeatureSourceSchemaCache();
+
+            Assert.AreEqual(0, pc.CachedClassDefinitions);
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+
+            var cls = conn.FeatureService.GetClassDefinition(fsId, "SHP_Schema:Parcels");
+
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+
+            var cls2 = conn.FeatureService.GetClassDefinition(fsId, "SHP_Schema:Parcels");
+
+            Assert.AreEqual(0, pc.CachedFeatureSources);
+            Assert.AreEqual(1, pc.CachedClassDefinitions);
+            //Each cached instance returned is a clone
+            Assert.False(object.ReferenceEquals(cls, cls2));
+        }
+
         protected void CreateTestDataStore(IServerConnection conn, string fsId, ref FeatureSchema schema, ref ClassDefinition cls)
         {
             schema = new FeatureSchema("Default", "");
@@ -303,6 +377,18 @@
         }
 
         [Test]
+        public void TestFeatureSourceCaching()
+        {
+            base.TestFeatureSourceCaching("LocalNativeFeatureSourceCaching");
+        }
+
+        [Test]
+        public void TestClassDefinitionCaching()
+        {
+            base.TestClassDefinitionCaching("LocalNativeClassCaching");
+        }
+
+        [Test]
         public override void TestApplySchema()
         {
             base.TestApplySchema();
@@ -342,6 +428,18 @@
         }
 
         [Test]
+        public void TestFeatureSourceCaching()
+        {
+            base.TestFeatureSourceCaching("LocalFeatureSourceCaching");
+        }
+
+        [Test]
+        public void TestClassDefinitionCaching()
+        {
+            base.TestClassDefinitionCaching("LocalClassCaching");
+        }
+
+        [Test]
         public override void TestApplySchema()
         {
             base.TestApplySchema();

Modified: trunk/Tools/Maestro/MaestroAPITests/TestControl.cs
===================================================================
--- trunk/Tools/Maestro/MaestroAPITests/TestControl.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/MaestroAPITests/TestControl.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -40,7 +40,7 @@
         public const bool IgnoreLocalNativeRuntimeMapTests = true;
         public const bool IgnoreLocalNativePerformanceTests = true;
         public const bool IgnoreLocalNativeFeatureTests = true;
-        public const bool IgnoreGeoRestTests = false;
+        public const bool IgnoreGeoRestTests = true;
         public const bool IgnoreLocalFeatureTests = false;
         public const bool IgnoreSchemaTests = false;
         public const bool IgnoreSerializationTests = false;
@@ -61,7 +61,7 @@
 
     public class ConnectionUtil
     {
-        public static string Port { get { return ""; } }
+        public static string Port { get { return "8008"; } }
 
         public static IServerConnection CreateTestHttpConnectionWithGeoRest()
         {

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/PlatformConnectionBase.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/PlatformConnectionBase.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/PlatformConnectionBase.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -448,6 +448,10 @@
         public virtual void SetResourceXmlData(string resourceid, System.IO.Stream stream)
         {
             SetResourceXmlData(resourceid, stream, null);
+            int purged = PurgeCachedItemsOf(resourceid);
+#if DEBUG
+            System.Diagnostics.Trace.TraceInformation("{0} cached items purged for {1}", purged, resourceid);
+#endif
         }
 
         /// <summary>
@@ -1210,6 +1214,44 @@
         abstract public void SetResourceData(string resourceid, string dataname, ObjCommon.ResourceDataType datatype, System.IO.Stream stream, Utility.StreamCopyProgressDelegate callback);
 
         /// <summary>
+        /// Removes all cached items associated with the given feature source
+        /// </summary>
+        /// <param name="resourceId"></param>
+        /// <returns></returns>
+        protected int PurgeCachedItemsOf(string resourceId)
+        {
+            //All keys are encoded with the resource id at the beginning,
+            //so hunt down all matching keys starting with our resource id
+            //these will be queued for removal.
+            var purgeFsd = new List<string>();
+            foreach (var key in m_featureSchemaCache.Keys)
+            {
+                if (key.StartsWith(resourceId))
+                    purgeFsd.Add(key);
+            }
+
+            var purgeCls = new List<string>();
+            foreach (var key in m_classDefinitionCache.Keys)
+            {
+                if (key.StartsWith(resourceId))
+                    purgeCls.Add(key);
+            }
+
+            int removed = 0;
+            foreach (var key in purgeFsd)
+            {
+                if (m_featureSchemaCache.Remove(key))
+                    removed++;
+            }
+            foreach (var key in purgeCls)
+            {
+                if (m_classDefinitionCache.Remove(key))
+                    removed++;
+            }
+            return removed;
+        }
+
+        /// <summary>
         /// Saves the resource.
         /// </summary>
         /// <param name="resource">The resource.</param>
@@ -1402,13 +1444,6 @@
         /// Describes the feature source.
         /// </summary>
         /// <param name="resourceID">The resource ID.</param>
-        /// <returns></returns>
-        abstract public FeatureSourceDescription DescribeFeatureSource(string resourceID);
-
-        /// <summary>
-        /// Describes the feature source.
-        /// </summary>
-        /// <param name="resourceID">The resource ID.</param>
         /// <param name="schema">The schema.</param>
         /// <returns></returns>
         abstract public FeatureSchema DescribeFeatureSource(string resourceID, string schema);
@@ -1423,30 +1458,55 @@
         protected Dictionary<string, ClassDefinition> m_classDefinitionCache = new Dictionary<string, ClassDefinition>();
 
         /// <summary>
+        /// Calls the actual implementation of the DescribeFeatureSource API
+        /// </summary>
+        /// <param name="resourceId"></param>
+        /// <returns></returns>
+        protected abstract FeatureSourceDescription DescribeFeatureSourceInternal(string resourceId);
+
+        /// <summary>
         /// Gets the feature source description.
         /// </summary>
         /// <param name="resourceID">The resource ID.</param>
         /// <returns></returns>
-        public virtual FeatureSourceDescription GetFeatureSourceDescription(string resourceID)
+        public virtual FeatureSourceDescription DescribeFeatureSource(string resourceID)
         {
+            bool bFromCache = true;
             if (!m_featureSchemaCache.ContainsKey(resourceID))
             {
+                bFromCache = false;
+                var fsd = this.DescribeFeatureSourceInternal(resourceID);
                 try
                 {
-                    m_featureSchemaCache[resourceID] = this.DescribeFeatureSource(resourceID);
-                    foreach (ClassDefinition scm in m_featureSchemaCache[resourceID].AllClasses)
-                        m_classDefinitionCache[resourceID + "!" + scm.QualifiedName] = scm;
+                    //Cache a clone of each class definition
+                    m_featureSchemaCache[resourceID] = FeatureSourceDescription.Clone(fsd);
+                    foreach (ClassDefinition cls in fsd.AllClasses)
+                    {
+                        string classCacheKey = resourceID + "!" + cls.QualifiedName;
+                        m_classDefinitionCache[classCacheKey] = cls;
+                    }
                 }
                 catch
                 {
                     m_featureSchemaCache[resourceID] = null;
                 }
             }
-
-            return m_featureSchemaCache[resourceID];
-
+#if DEBUG
+            if (bFromCache)
+                System.Diagnostics.Trace.TraceInformation("Returning cached description for {0}", resourceID);
+#endif
+            //Return a clone to ensure immutability of cached one
+            return FeatureSourceDescription.Clone(m_featureSchemaCache[resourceID]);
         }
 
+        /// <summary>
+        /// Fetches the specified class definition
+        /// </summary>
+        /// <param name="resourceId"></param>
+        /// <param name="schemaName"></param>
+        /// <param name="className"></param>
+        /// <returns></returns>
+        protected abstract ClassDefinition GetClassDefinitionInternal(string resourceId, string schemaName, string className);
 
         /// <summary>
         /// Gets the class definition.
@@ -1456,42 +1516,63 @@
         /// <returns></returns>
         public virtual ClassDefinition GetClassDefinition(string resourceID, string className)
         {
-            /*if (schema != null && schema.IndexOf(":") > 0)
-                schema = schema.Substring(0, schema.IndexOf(":"));*/
+            //NOTE: To prevent ambiguity, only class definitions queried with qualified
+            //names are cached. Un-qualified ones will call directly into the implementing
+            //GetClassDefinition API
+            bool bQualified = className.Contains(":");
+            string classCacheKey = resourceID + "!" + className;
+            ClassDefinition cls = null;
+            bool bStoreInCache = true;
+            bool bFromCache = false;
 
-            FeatureSourceDescription desc = null;
-            //If it is missing, just get the entire schema, and hope that we will need the others
-            //Some providers actually return the entire list even when asked for a particular schema
-            if (!m_featureSchemaCache.ContainsKey(resourceID + "!" + className))
-                desc = GetFeatureSourceDescription(resourceID);
-            if (!m_classDefinitionCache.ContainsKey(resourceID + "!" + className))
-                m_classDefinitionCache[resourceID + "!" + className] = null;
+            //We don't interrogate the Feature Source Description cache because part of
+            //caching a Feature Source Description is to cache all the classes within
+            if (m_classDefinitionCache.ContainsKey(classCacheKey))
+            {
+                cls = m_classDefinitionCache[classCacheKey];
+                bStoreInCache = false;
+                bFromCache = true;
+            }
+            else
+            {
+                if (bQualified)
+                {
+                    var tokens = className.Split(':');
+                    cls = GetClassDefinitionInternal(resourceID, tokens[0], tokens[1]);
+                }
+                else
+                {
+                    cls = GetClassDefinitionInternal(resourceID, null, className);
+                }
+            }
 
-            var cls = m_classDefinitionCache[resourceID + "!" + className];
-            if (cls == null)
+            //Only class definitions queried with qualified names can be cached
+            if (bStoreInCache && !bQualified)
+                bStoreInCache = false;
+
+#if DEBUG
+            if (bFromCache)
+                System.Diagnostics.Trace.TraceInformation("Returning cached class ({0}) for {1}", className, resourceID);
+#endif
+
+            if (cls != null)
             {
-                //Try non qualified
-                if (desc != null)
+                if (bStoreInCache)
                 {
-                    if (desc.Schemas.Length == 1)
-                    {
-                        return desc.GetClass(desc.SchemaNames[0], className);
-                    }
-                    else
-                    {
-                        //Since this is not qualified, just find the first matching
-                        //class by its name, regardless of its parent
-                        foreach (var klass in desc.AllClasses)
-                        {
-                            if (klass.Name == className)
-                                return klass;
-                        }
-                    }
+                    m_classDefinitionCache[classCacheKey] = cls;
                 }
+
+                //Return a clone of the cached object to ensure immutability of
+                //the original
+                return ClassDefinition.Clone(cls);
             }
-            return cls;
+            return null;
         }
 
+        internal int CachedFeatureSources { get { return m_featureSchemaCache.Count; } }
+
+        internal int CachedClassDefinitions { get { return m_classDefinitionCache.Count; } }
+
         /// <summary>
         /// Resets the feature source schema cache.
         /// </summary>

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/ClassDefinition.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/ClassDefinition.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/ClassDefinition.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -381,6 +381,11 @@
                     clone.AddProperty(clonedProp);
                 }
             }
+            clone.DefaultGeometryPropertyName = source.DefaultGeometryPropertyName;
+            clone.IsAbstract = source.IsAbstract;
+            clone.IsComputed = source.IsComputed;
+            if (source.Parent != null)
+                clone.Parent = new FeatureSchema(source.Parent.Name, source.Parent.Description);
             //TODO: Base Class
             return clone;
         }

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSchema.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSchema.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSchema.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -189,5 +189,20 @@
             if (cls != null)
                 RemoveClass(cls);
         }
+
+        /// <summary>
+        /// Creates a clone of the specified instance
+        /// </summary>
+        /// <param name="fs">The instance to clone</param>
+        /// <returns></returns>
+        public static FeatureSchema Clone(FeatureSchema fs)
+        {
+            var clone = new FeatureSchema(fs.Name, fs.Description);
+            foreach (var cls in fs.Classes)
+            {
+                clone.AddClass(ClassDefinition.Clone(cls));
+            }
+            return clone;
+        }
     }
 }

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSourceDescription.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSourceDescription.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Schema/FeatureSourceDescription.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -147,119 +147,7 @@
             return null;
         }
 
-        #region old impl
-        /*
-        private ClassDefinition[] m_classes;
-
-        private string[] m_schemaNames;
-
         /// <summary>
-        /// Initializes a new instance of the <see cref="FeatureSourceDescription"/> class.
-        /// </summary>
-        /// <param name="stream">The stream.</param>
-		public FeatureSourceDescription(System.IO.Stream stream)
-		{
-			XmlDocument doc = new XmlDocument();
-			doc.Load(stream);
-
-			if (doc.FirstChild.Name != "xml")
-				throw new Exception("Bad document");
-
-            XmlNode root;
-            if (doc.ChildNodes.Count == 2 && doc.ChildNodes[1].Name == "fdo:DataStore")
-                root = doc.ChildNodes[1];
-            else if (doc.ChildNodes.Count != 2 || doc.ChildNodes[1].Name != "xs:schema")
-                throw new Exception("Bad document");
-            else
-                root = doc;
-
-			XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
-			mgr.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
-			mgr.AddNamespace("gml", "http://www.opengis.net/gml");
-			mgr.AddNamespace("fdo", "http://fdo.osgeo.org/schemas");
-
-            var keys = new Dictionary<string, string[]>();
-            var classMap = new Dictionary<string, ClassDefinition>();
-            XmlNodeList lst = root.SelectNodes("xs:schema/xs:complexType[@abstract='false']", mgr);
-            m_classes = new ClassDefinition[lst.Count];
-            for (int i = 0; i < m_classes.Length; i++)
-            {
-                m_classes[i] = new ClassDefinition(lst[i], mgr);
-                classMap.Add(m_classes[i].QualifiedName, m_classes[i]);
-            }
-            XmlNodeList keyNodes = root.SelectNodes("xs:schema/xs:element[@abstract='false']", mgr);
-            foreach (XmlNode keyNode in keyNodes)
-            {
-                var typeAttr = keyNode.Attributes["type"];
-                if (typeAttr != null)
-                {
-                    string clsName = typeAttr.Value.Substring(0, typeAttr.Value.Length - 4); //class name is suffixed with type
-                    if (classMap.ContainsKey(clsName))
-                    {
-                        List<string> keyFieldNames = new List<string>();
-
-                        var cls = classMap[clsName];
-                        XmlNodeList keyFields = keyNode.SelectNodes("xs:key/xs:field", mgr);
-                        foreach (XmlNode keyField in keyFields)
-                        {
-                            var xpathAttr = keyField.Attributes["xpath"];
-                            if (xpathAttr != null)
-                            {
-                                keyFieldNames.Add(xpathAttr.Value);
-                            }
-                        }
-
-                        cls.MarkIdentityProperties(keyFieldNames);
-                    }
-                }
-            }
-
-            var snames = new List<string>();
-            foreach (string qn in classMap.Keys)
-            {
-                string[] tokens = qn.Split(':');
-                if (!snames.Contains(tokens[0]))
-                    snames.Add(tokens[0]);
-            }
-            m_schemaNames = snames.ToArray();
-		}
-
-        /// <summary>
-        /// Gets the schema names.
-        /// </summary>
-        /// <value>The schema names.</value>
-        public string[] SchemaNames { get { return m_schemaNames; } }
-
-        /// <summary>
-        /// Gets the classes.
-        /// </summary>
-        /// <value>The classes.</value>
-		public ClassDefinition[] Classes { get { return m_classes; } }
-
-        /// <summary>
-        /// Gets the <see cref="OSGeo.MapGuide.MaestroAPI.ClassDefinition"/> at the specified index.
-        /// </summary>
-        /// <value></value>
-		public ClassDefinition this[int index] { get { return m_classes[index]; } }
-        /// <summary>
-        /// Gets the <see cref="OSGeo.MapGuide.MaestroAPI.ClassDefinition"/> at the specified index.
-        /// </summary>
-        /// <value></value>
-		public ClassDefinition this[string index] 
-		{
-			get 
-			{
-				for(int i =0 ;i<m_classes.Length; i++)
-					if (m_classes[i].Name == index)
-						return m_classes[i];
-
-				throw new OverflowException("No such item found: " + index);
-			}
-        }
-        */
-        #endregion
-
-        /// <summary>
         /// Gets whether there are any class definitions
         /// </summary>
         /// <returns></returns>
@@ -290,5 +178,31 @@
 
             return GetClass(tokens[0], tokens[1]);
         }
+
+        /// <summary>
+        /// Internal ctor for cloning purposes
+        /// </summary>
+        /// <param name="schemas"></param>
+        /// <param name="bPartial"></param>
+        internal FeatureSourceDescription(List<FeatureSchema> schemas, bool bPartial)
+        {
+            this.Schemas = schemas.ToArray();
+            this.IsPartial = bPartial;
+        }
+
+        /// <summary>
+        /// Creates a clone of the specified instance
+        /// </summary>
+        /// <param name="fsd">The instance to clone</param>
+        /// <returns></returns>
+        public static FeatureSourceDescription Clone(FeatureSourceDescription fsd)
+        {
+            var schemas = new List<FeatureSchema>();
+            foreach (var fs in fsd.Schemas)
+            {
+                schemas.Add(FeatureSchema.Clone(fs));
+            }
+            return new FeatureSourceDescription(schemas, fsd.IsPartial);
+        }
     }
 }

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Services/IFeatureService.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Services/IFeatureService.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI/Services/IFeatureService.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -211,7 +211,7 @@
         /// Gets the specified class definition
         /// </summary>
         /// <param name="resourceID"></param>
-        /// <param name="schema"></param>
+        /// <param name="className">The feature class name. You can pass a qualified class name to be explicit about which class definition you are after</param>
         /// <returns></returns>
         ClassDefinition GetClassDefinition(string resourceID, string className);
 

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/HttpServerConnection.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/HttpServerConnection.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/HttpServerConnection.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -673,7 +673,7 @@
             return QueryFeatureSourceCore(true, resourceID, schema, filter, null, aggregateFunctions);
         }
 
-		public override FeatureSourceDescription DescribeFeatureSource(string resourceID)
+        protected override FeatureSourceDescription DescribeFeatureSourceInternal(string resourceID)
 		{
             ResourceIdentifier.Validate(resourceID, ResourceTypes.FeatureSource);
             string req = m_reqBuilder.DescribeSchema(resourceID);
@@ -1809,6 +1809,16 @@
             }
         }
 
+        protected override ClassDefinition GetClassDefinitionInternal(string resourceId, string schemaName, string className)
+        {
+            var req = m_reqBuilder.GetClassDefinition(resourceId, schemaName, className);
+            using (var s = this.OpenRead(req))
+            {
+                var fsd = new FeatureSourceDescription(s);
+                return fsd.Schemas[0].Classes[0];
+            }
+        }
+
         public override IServerConnection Clone()
         {
             if (this.IsAnonymous)

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/RequestBuilder.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/RequestBuilder.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Http/RequestBuilder.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -1515,6 +1515,26 @@
             return m_hosturi + "?" + EncodeParameters(param);
         }
 
+        public string GetClassDefinition(string resourceId, string schemaName, string className)
+        {
+            NameValueCollection param = new NameValueCollection();
+            param.Add("OPERATION", "GETCLASSDEFINITION");
+            param.Add("VERSION", "1.0.0");
+            param.Add("SESSION", m_sessionID);
+            param.Add("FORMAT", "text/xml");
+            param.Add("CLIENTAGENT", m_userAgent);
+
+            if (m_locale != null)
+                param.Add("LOCALE", m_locale);
+
+            param.Add("RESOURCEID", resourceId);
+            if (!string.IsNullOrEmpty(schemaName))
+                param.Add("SCHEMA", schemaName);
+            param.Add("CLASSNAME", className);
+
+            return m_hosturi + "?" + EncodeParameters(param);
+        }
+
         public string GetSiteInfo()
         {
             NameValueCollection param = new NameValueCollection();

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Local/LocalConnection.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Local/LocalConnection.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Local/LocalConnection.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -485,7 +485,7 @@
             throw new Exception("Unable to find class: " + parts[1] + " in schema " + parts[0]);
         }
 
-        public override FeatureSourceDescription DescribeFeatureSource(string resourceID)
+        protected override FeatureSourceDescription DescribeFeatureSourceInternal(string resourceID)
         {
             var fes = GetFeatureService();
             MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(fes.DescribeSchemaAsXml(new MgResourceIdentifier(resourceID), "")));
@@ -573,6 +573,12 @@
             return names.ToArray();
         }
 
+        protected override ClassDefinition GetClassDefinitionInternal(string resourceId, string schemaName, string className)
+        {
+            var cls = GetFeatureService().GetClassDefinition(new MgResourceIdentifier(resourceId), schemaName, className);
+            return Native.Utility.ConvertClassDefinition(cls);
+        }
+
         public override Version SiteVersion
         {
             get { return new Version(2, 4, 0, 0); }

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/LocalNativeConnection.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/LocalNativeConnection.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/LocalNativeConnection.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -351,8 +351,7 @@
             return AggregateQueryFeatureSourceCore(resourceID, schema, filter, null, aggregateFunctions);
         }
 
-
-		public override FeatureSourceDescription DescribeFeatureSource(string resourceID)
+        protected override FeatureSourceDescription DescribeFeatureSourceInternal(string resourceID)
 		{
 			MgFeatureService fes = this.Connection.CreateService(MgServiceType.FeatureService) as MgFeatureService;
 			System.IO.MemoryStream ms = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(fes.DescribeSchemaAsXml(new MgResourceIdentifier(resourceID), "")));
@@ -1052,6 +1051,13 @@
             return names.ToArray();
         }
 
+        protected override ClassDefinition GetClassDefinitionInternal(string resourceId, string schemaName, string className)
+        {
+            var fsvc = (MgFeatureService)this.Connection.CreateService(MgServiceType.FeatureService);
+            var cls = fsvc.GetClassDefinition(new MgResourceIdentifier(resourceId), schemaName, className);
+            return Native.Utility.ConvertClassDefinition(cls);
+        }
+
         public override IServerConnection Clone()
         {
             var initP = new NameValueCollection();

Modified: trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/Utility.cs
===================================================================
--- trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/Utility.cs	2012-03-13 10:24:58 UTC (rev 6544)
+++ trunk/Tools/Maestro/OSGeo.MapGuide.MaestroAPI.Native/Utility.cs	2012-03-13 15:15:03 UTC (rev 6545)
@@ -250,7 +250,9 @@
                     MgDataPropertyDefinition mgData = (MgDataPropertyDefinition)prop;
                     var dp = ConvertDataProperty(mgData);
 
-                    bool identity = (mgClass.GetIdentityProperties().Contains(mgData));
+                    //API Bug? passing object reference gives incorrect result for identity
+                    //properties
+                    bool identity = (mgClass.GetIdentityProperties().Contains(prop.Name));
                     cls.AddProperty(dp, identity);
                 }
                 else if (prop.PropertyType == MgFeaturePropertyType.GeometricProperty)



More information about the mapguide-commits mailing list