diff --git a/src/Spring/Spring.Core/Objects/Factory/Xml/NamespaceParserRegistry.cs b/src/Spring/Spring.Core/Objects/Factory/Xml/NamespaceParserRegistry.cs index 73caf7af..38a203f9 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Xml/NamespaceParserRegistry.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Xml/NamespaceParserRegistry.cs @@ -23,8 +23,10 @@ using System; using System.Collections; using System.Collections.Specialized; +using System.Reflection; using System.Xml; using System.Xml.Schema; +using Spring.Collections; using Spring.Core; using Spring.Core.IO; using Spring.Core.TypeResolution; @@ -76,6 +78,7 @@ namespace Spring.Objects.Factory.Xml #region Fields private readonly static IDictionary parsers; + private readonly static IDictionary wellknownNamespaceParserTypeNames; #if !NET_2_0 private readonly static XmlSchemaCollection schemas; @@ -97,18 +100,54 @@ namespace Spring.Objects.Factory.Xml schemas = new XmlSchemaSet(); schemas.XmlResolver = new XmlResourceUrlResolver(); #endif + wellknownNamespaceParserTypeNames = new CaseInsensitiveHashtable(); + wellknownNamespaceParserTypeNames["http://www.springframework.net/tx"] = "Spring.Transaction.Config.TxNamespaceParser, Spring.Data"; + wellknownNamespaceParserTypeNames["http://www.springframework.net/aop"] = "Spring.Aop.Config.AopNamespaceParser, Spring.Aop"; + wellknownNamespaceParserTypeNames["http://www.springframework.net/db"] = "Spring.Data.Config.DatabaseNamespaceParser, Spring.Data"; + wellknownNamespaceParserTypeNames["http://www.springframework.net/remoting"] = "Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services"; + wellknownNamespaceParserTypeNames["http://www.springframework.net/nms"] = "Spring.Messaging.Nms.Config.NmsNamespaceParser, Spring.Messaging.Nms"; + wellknownNamespaceParserTypeNames["http://www.springframework.net/validation"] = "Spring.Validation.Config.ValidationNamespaceParser, Spring.Core"; + Reset(); + } + + /// + /// Reset the list of registered parsers to "factory"-setting + /// + /// use for unit tests only + public static void Reset() + { //TODO - externalize default list of parsers. RegisterParser(new ObjectsNamespaceParser()); - //This is done simple as a means to avoid cyclic dependencies with Factory.Xml - //which implementations of parsers typically use. - RegisterParser(ObjectUtils.InstantiateType(typeof(NamespaceParserRegistry).Assembly, - "Spring.Validation.Config.ValidationNamespaceParser") as INamespaceParser); - // register custom config parsers - ConfigurationUtils.GetSection(ConfigParsersSectionName); - } - + ConfigurationUtils.GetSection(ConfigParsersSectionName); + } + + /// + /// Registers the type for wellknown namespaces + /// + /// true if the parser could be registered, false otherwise + internal static bool RegisterWellknownNamespaceParserType(string namespaceUri) + { + if (parsers[namespaceUri] != null) return true; + + if (wellknownNamespaceParserTypeNames.Contains(namespaceUri)) + { + string parserTypeName = (string) wellknownNamespaceParserTypeNames[namespaceUri]; + // assume, that all Spring.XXX assemblies have same version + public key + // get the ", Version=x.x.x.x, Culture=neutral, PublicKeyToken=65e474d141e25e07" part of Spring.Core and append it + string name = typeof(NamespaceParserRegistry).Assembly.GetName().Name; + string fullname = typeof(NamespaceParserRegistry).Assembly.GetName().FullName; + string versionCulturePublicKey = fullname.Substring(name.Length); + + parserTypeName = parserTypeName + versionCulturePublicKey; + Type parserType = Type.GetType(parserTypeName, true); + RegisterParser(parserType); + return true; + } + return false; + } + /// /// Constructs a "assembly://..." qualified schemaLocation url using the given type /// to obtain the assembly name. @@ -134,7 +173,16 @@ namespace Spring.Objects.Factory.Xml /// public static INamespaceParser GetParser(string namespaceURI) { - return (INamespaceParser) parsers[namespaceURI]; + INamespaceParser parser = (INamespaceParser) parsers[namespaceURI]; + if (parser == null) + { + bool ok = RegisterWellknownNamespaceParserType(namespaceURI); + if (ok) + { + parser = (INamespaceParser) parsers[namespaceURI]; + } + } + return parser; } /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Xml/XmlObjectDefinitionReader.cs b/src/Spring/Spring.Core/Objects/Factory/Xml/XmlObjectDefinitionReader.cs index ee1d93d6..87900434 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Xml/XmlObjectDefinitionReader.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Xml/XmlObjectDefinitionReader.cs @@ -53,6 +53,42 @@ namespace Spring.Objects.Factory.Xml /// Rick Evans (.NET) public class XmlObjectDefinitionReader : AbstractObjectDefinitionReader { + #region Utility Classes + + /// + /// For retrying the parse process + /// + private class RetryParseException : Exception + { + public RetryParseException() + {} + } + +#if !NET_2_0 + private class ValidationEventHandlerWrapper + { + private XmlReader _sender; + private XmlObjectDefinitionReader _owner; + + public XmlReader Reader + { + set { _sender = value; } + } + + public ValidationEventHandlerWrapper(XmlObjectDefinitionReader owner) + { + _owner = owner; + } + + public void HandleValidation(object sender, ValidationEventArgs args) + { + _owner.HandleValidation(_sender,args); + } + } +#endif + + #endregion + #region Fields [NonSerialized] @@ -208,29 +244,27 @@ namespace Spring.Objects.Factory.Xml { try { - XmlReader reader; - - if (SystemUtils.MonoRuntime) + // create local copy of data + byte[] xmlData = IOUtils.ToByteArray( stream ); + + XmlDocument doc; + // loop until no unregistered, wellknown namespaces left + while(true) { - reader = XmlUtils.CreateReader(stream); + XmlReader reader = null; + try + { + MemoryStream xmlDataStream = new MemoryStream(xmlData); + reader = CreateValidatingReader(xmlDataStream); + doc = new ConfigXmlDocument(); + doc.Load(reader); + break; + } + catch(RetryParseException) + { + if (reader != null) reader.Close(); + } } - else - { - reader = XmlUtils.CreateValidatingReader(stream, Resolver, NamespaceParserRegistry.GetSchemas(), - new ValidationEventHandler(HandleValidation)); - } - - #region Instrumentation - - if (log.IsDebugEnabled) - { - log.Debug("Using the following XmlReader implementation : " + reader.GetType()); - } - - #endregion - - XmlDocument doc = new ConfigXmlDocument(); - doc.Load(reader); return RegisterObjectDefinitions(doc, resource); } catch (XmlException ex) @@ -253,6 +287,37 @@ namespace Spring.Objects.Factory.Xml { throw new ObjectDefinitionStoreException("Unexpected exception parsing XML document from " + resource.Description + "Inner exception message= " + ex.Message, ex); } + } + + private XmlReader CreateValidatingReader(MemoryStream stream) + { + XmlReader reader; + if (SystemUtils.MonoRuntime) + { + reader = XmlUtils.CreateReader(stream); + } + else + { +#if !NET_2_0 + // only because 1.0/1.1 don't pass the sender into the handler callback... + ValidationEventHandlerWrapper validationEventHandlerWrapper = new ValidationEventHandlerWrapper(this); + reader = XmlUtils.CreateValidatingReader(stream, Resolver, NamespaceParserRegistry.GetSchemas(), + new ValidationEventHandler(validationEventHandlerWrapper.HandleValidation)); + validationEventHandlerWrapper.Reader = reader; +#else + reader = XmlUtils.CreateValidatingReader(stream, Resolver, NamespaceParserRegistry.GetSchemas(), HandleValidation); +#endif + } + + #region Instrumentation + + if (log.IsDebugEnabled) + { + log.Debug("Using the following XmlReader implementation : " + reader.GetType()); + } + return reader; + + #endregion } /// @@ -265,6 +330,20 @@ namespace Spring.Objects.Factory.Xml if (args.Severity == XmlSeverityType.Error) { XmlSchemaException ex = args.Exception; + XmlReader xmlReader = (XmlReader) sender; + if (!NamespaceParserRegistry.GetSchemas().Contains(xmlReader.NamespaceURI) +#if NET_2_0 + && ex is XmlSchemaValidationException +#endif + ) + { + // try wellknown parsers + bool registered = NamespaceParserRegistry.RegisterWellknownNamespaceParserType(xmlReader.NamespaceURI); + if (registered) + { + throw new RetryParseException(); + } + } #if !NET_2_0 // ignore validation errors for well-known 'xml' namespace. This seems to be a bug in net 1.0 + 1.1 if (ex.Message.IndexOf("http://www.w3.org/XML/1998/namespace:") > -1) diff --git a/src/Spring/Spring.Core/Spring.Core.2003.csproj b/src/Spring/Spring.Core/Spring.Core.2003.csproj index 9f584928..15eeb770 100644 --- a/src/Spring/Spring.Core/Spring.Core.2003.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2003.csproj @@ -2392,6 +2392,11 @@ SubType = "Code" BuildAction = "Compile" /> + + diff --git a/src/Spring/Spring.Core/Util/IoUtils.cs b/src/Spring/Spring.Core/Util/IoUtils.cs new file mode 100644 index 00000000..a0be58fe --- /dev/null +++ b/src/Spring/Spring.Core/Util/IoUtils.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; + +namespace Spring.Util +{ + /// + /// Utility methods for IO handling + /// + internal sealed class IOUtils + { + private IOUtils() + { + throw new InvalidOperationException("instantiation not supported"); + } + + /// + /// Copies one stream into another. + /// (Don't forget to call on the destination stream!) + /// + /// + /// Does not close the input stream! + /// + public static void CopyStream( Stream src, Stream dest ) + { + int bufferSize = 2048; + byte[] buffer = new byte[bufferSize]; + + int bytesRead = src.Read(buffer, 0, bufferSize); + while( bytesRead == bufferSize ) + { + dest.Write( buffer, 0, bytesRead ); + bytesRead = src.Read( buffer, 0, bufferSize ); + } + if (bytesRead > 0) + { + dest.Write( buffer, 0, bytesRead ); + } + } + + /// + /// Reads a stream into a byte array. + /// + /// + /// Does not close the input stream! + /// + public static byte[] ToByteArray( Stream src ) + { + MemoryStream stm = new MemoryStream(); + CopyStream( src, stm ); + stm.Flush(); + return stm.ToArray(); + } + + } +} diff --git a/test/Spring/Spring.Aop.Tests/Aop/Config/AopNamespaceParserTests.cs b/test/Spring/Spring.Aop.Tests/Aop/Config/AopNamespaceParserTests.cs index 21e5cc07..b3e3d4d0 100644 --- a/test/Spring/Spring.Aop.Tests/Aop/Config/AopNamespaceParserTests.cs +++ b/test/Spring/Spring.Aop.Tests/Aop/Config/AopNamespaceParserTests.cs @@ -44,8 +44,9 @@ namespace Spring.Aop.Config [SetUp] public void Setup() - { - NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); + { + // IS WELLKNOWN NOW + //NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); //ctx = new XmlApplicationContext( "assembly://Spring.Aop.Tests/Spring.Aop.Config/AopNamespaceParserTests.xml"); ctx = new XmlApplicationContext(ReadOnlyXmlTestResource.GetFilePath("AopNamespaceParserTests.xml", this.GetType())); } diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/NamespaceParserRegistryTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/NamespaceParserRegistryTests.cs index ddf412f3..a44966d3 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/NamespaceParserRegistryTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/NamespaceParserRegistryTests.cs @@ -64,7 +64,7 @@ namespace Spring.Objects.Factory.Xml [Test] public void CanLoadSchemaImportingOtherSchemaByRelativePath() { - string schemaLocation = GetAssemblyResource( this.GetType(), "NamespaceParserRegistryTests_TestSchema.xsd" ); + string schemaLocation = TestResourceLoader.GetAssemblyResourceUri( this.GetType(), "NamespaceParserRegistryTests_TestSchema.xsd" ); NamespaceParserRegistry.RegisterParser(new TestNamespaceParser(), "http://www.example.com/brief", schemaLocation); XmlReader vr = XmlUtils.CreateValidatingReader( new StringResource( @" @@ -74,9 +74,5 @@ namespace Spring.Objects.Factory.Xml newDoc.Load(vr); } #endif - private string GetAssemblyResource( Type hint, string name) - { - return "assembly://" + hint.Assembly.FullName.Split(',')[0].Trim() + "/" + hint.Namespace + "/" + name; - } } } \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests.cs index 97225341..adc9f4e6 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests.cs @@ -24,7 +24,9 @@ using System; using System.Xml; using NUnit.Framework; using Spring.Core.IO; +using Spring.Objects.Factory.Config; using Spring.Objects.Factory.Support; +using Spring.Validation; #endregion @@ -65,6 +67,49 @@ namespace Spring.Objects.Factory.Xml reader.LoadObjectDefinitions(new ReadOnlyXmlTestResource("/dev/null")); } + [Test] + public void AutoRegistersWellknownNamespaceParser() + { + try + { + NamespaceParserRegistry.Reset(); + + DefaultListableObjectFactory of = new DefaultListableObjectFactory(); + XmlObjectDefinitionReader reader = new XmlObjectDefinitionReader(of); + reader.LoadObjectDefinitions(new StringResource( + @" + + + +")); + Assert.AreEqual(typeof(RequiredValidator), of.GetObject("tripValidator").GetType()); + } + finally + { + NamespaceParserRegistry.Reset(); + } + } + + [Test] + [ExpectedException(typeof(ObjectDefinitionStoreException))] + public void ThrowsOnUnknownNamespaceUri() + { + NamespaceParserRegistry.Reset(); + + DefaultListableObjectFactory of = new DefaultListableObjectFactory(); + XmlObjectDefinitionReader reader = new XmlObjectDefinitionReader(of); + reader.LoadObjectDefinitions(new StringResource( + @" + + + +")); + Assert.Fail(); + } + + [Test] public void WhitespaceValuesArePreservedForValueAttribute() { @@ -105,6 +150,7 @@ namespace Spring.Objects.Factory.Xml Assert.AreEqual(string.Empty, ((TestObject) of.GetObject("test4")).Name); } + [Test] public void WhitespaceValuesArePreservedForValueElementWhenSpaceIsSetToPreserve() { diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests_CustomSchema.xsd b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests_CustomSchema.xsd new file mode 100644 index 00000000..05cb42c6 --- /dev/null +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Xml/XmlObjectDefinitionReaderTests_CustomSchema.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Core.Tests/StandardsComplianceTest.cs b/test/Spring/Spring.Core.Tests/StandardsComplianceTest.cs index 3f9e600a..a96d40fa 100644 --- a/test/Spring/Spring.Core.Tests/StandardsComplianceTest.cs +++ b/test/Spring/Spring.Core.Tests/StandardsComplianceTest.cs @@ -76,7 +76,8 @@ namespace Spring protected void ProcessAssembly (Assembly a) { foreach (Type t in a.GetTypes ()) { - if (IsCheckedType (t)) { + if ( (t.IsPublic||t.IsNestedPublic) + && IsCheckedType (t)) { CheckStandardsCompliance (a, t); } } diff --git a/test/Spring/Spring.Core.Tests/TestResourceLoader.cs b/test/Spring/Spring.Core.Tests/TestResourceLoader.cs index 289b5f8b..126c4b55 100644 --- a/test/Spring/Spring.Core.Tests/TestResourceLoader.cs +++ b/test/Spring/Spring.Core.Tests/TestResourceLoader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Net; +using System.Reflection; using System.Web; using System.Xml; using NUnit.Framework; @@ -99,5 +100,15 @@ namespace Spring Assert.IsNotNull(stm, "Resource '{0}' in assembly '{1}' not found", resname, contextType.Assembly.FullName); return stm; } + + /// + /// returns an "assembly://" uri for the specified manifest resource, scoped by the namespace of the specified type. + /// ("assembly://hint.assemblyname_without_version/hint.Namespace/name") + /// + /// + public static string GetAssemblyResourceUri( Type hint, string name) + { + return "assembly://" + hint.Assembly.FullName.Split(',')[0].Trim() + "/" + hint.Namespace + "/" + name; + } } } diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs index c7dfefc3..3aa439df 100644 --- a/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs +++ b/test/Spring/Spring.Data.Integration.Tests/Data/DTCTests.cs @@ -46,8 +46,9 @@ namespace Spring.Data //BasicConfigurator.Configure(); Console.WriteLine("Hello"); LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(new NameValueCollection()); - NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); + + //WELLKNOWN: NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); + //WELLKNOWN: NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); string ctxName = "DTCAppContext.xml"; // for .NET 2.0 //string ctxName = "DTC1.1AppContext.xml"; // for .NET 1.1 ctx = diff --git a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs index 8d40d2f1..293f8318 100644 --- a/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs +++ b/test/Spring/Spring.Data.Integration.Tests/Data/TestTxIsolationLevelTests.cs @@ -56,9 +56,10 @@ namespace Spring.Data [SetUp] public void RollbackTestSetup() { - NamespaceParserRegistry.RegisterParser(typeof (DatabaseNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof (TxNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof (AopNamespaceParser)); + // WELLKNOWN +// NamespaceParserRegistry.RegisterParser(typeof (DatabaseNamespaceParser)); +// NamespaceParserRegistry.RegisterParser(typeof (TxNamespaceParser)); +// NamespaceParserRegistry.RegisterParser(typeof (AopNamespaceParser)); IApplicationContext ctx = new XmlApplicationContext( "assembly://Spring.Data.Integration.Tests/Spring.Data/TestTxIsolationLevel.xml"); diff --git a/test/Spring/Spring.Data.Tests/Transaction/Config/TxNamespaceParserTests.cs b/test/Spring/Spring.Data.Tests/Transaction/Config/TxNamespaceParserTests.cs index b36e3edc..40c98aeb 100644 --- a/test/Spring/Spring.Data.Tests/Transaction/Config/TxNamespaceParserTests.cs +++ b/test/Spring/Spring.Data.Tests/Transaction/Config/TxNamespaceParserTests.cs @@ -45,8 +45,8 @@ namespace Spring.Transaction.Config [SetUp] public void SetUp() { - NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); - NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); + //WELLKNOWN: NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser)); + //WELLKNOWN: NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser)); ctx = new XmlApplicationContext("assembly://Spring.Data.Tests/Spring.Transaction.Config/TxNamespaceParserTests.xml"); } diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs index 73f301cf..c36c01c6 100644 --- a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs @@ -58,7 +58,8 @@ namespace Spring.Messaging.Nms.Config [SetUp] public void Setup() { - NamespaceParserRegistry.RegisterParser(typeof(NmsNamespaceParser)); + // WELLKNOWN + //NamespaceParserRegistry.RegisterParser(typeof(NmsNamespaceParser)); ctx = new XmlApplicationContext(ReadOnlyXmlTestResource.GetFilePath("NmsNamespaceHandlerTests.xml", GetType())); mocks = new MockRepository(); } diff --git a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj index 4abdc4fb..85f925e0 100644 --- a/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj +++ b/test/Spring/Spring.Web.Tests/Spring.Web.Tests.2003.csproj @@ -321,7 +321,7 @@ />