#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using Spring.Core.IO;
using Spring.Objects.Factory.Config;
using Spring.Objects.Factory.Support;
using Spring.Util;
#endregion
namespace Spring.Objects.Factory.Xml{
/// <summary>
/// Object definition reader for Spring's default XML object definition format.
/// </summary>
/// <remarks>
/// <p>
/// Typically applied to a
/// <see cref="Spring.Objects.Factory.Support.DefaultListableObjectFactory"/> instance.
/// </p>
/// <p>
/// This class registers each object definition with the given object factory superclass,
/// and relies on the latter's implementation of the
/// <see cref="Spring.Objects.Factory.Support.IObjectDefinitionRegistry"/> interface.
/// </p>
/// <p>
/// It supports singletons, prototypes, and references to either of these kinds of object.
/// </p>
/// </remarks>
/// <author>Juergen Hoeller</author>
/// <author>Rick Evans (.NET)</author>
public class XmlObjectDefinitionReader : AbstractObjectDefinitionReader
{
#region Utility Classes
/// <summary>
/// For retrying the parse process
/// </summary>
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]
private XmlResolver resolver;
private Type documentReaderType;
private INamespaceParserResolver namespaceParserResolver;
private IObjectDefinitionFactory objectDefinitionFactory;
#endregion
#region Constructor (s) / Destructor
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Objects.Factory.Xml.XmlObjectDefinitionReader"/> class.
/// </summary>
/// <param name="registry">
/// The <see cref="Spring.Objects.Factory.Support.IObjectDefinitionRegistry"/>
/// instance that this reader works on.
/// </param>
public XmlObjectDefinitionReader(IObjectDefinitionRegistry registry)
: this(registry, new XmlUrlResolver())
{ }
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Objects.Factory.Xml.XmlObjectDefinitionReader"/> class.
/// </summary>
/// <param name="registry">
/// The <see cref="Spring.Objects.Factory.Support.IObjectDefinitionRegistry"/>
/// instance that this reader works on.
/// </param>
/// <param name="resolver">
/// The <see cref="System.Xml.XmlResolver"/>to be used for parsing.
/// </param>
public XmlObjectDefinitionReader(IObjectDefinitionRegistry registry, XmlResolver resolver)
: this(registry, resolver, new DefaultObjectDefinitionFactory())
{
Resolver = resolver;
}
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Objects.Factory.Xml.XmlObjectDefinitionReader"/> class.
/// </summary>
/// <param name="registry">
/// The <see cref="Spring.Objects.Factory.Support.IObjectDefinitionRegistry"/>
/// instance that this reader works on.
/// </param>
/// <param name="resolver">
/// The <see cref="System.Xml.XmlResolver"/>to be used for parsing.
/// </param>
/// <param name="objectDefinitionFactory">the <see cref="IObjectDefinitionFactory"/> to use for creating new <see cref="IObjectDefinition"/>s</param>
protected XmlObjectDefinitionReader(IObjectDefinitionRegistry registry, XmlResolver resolver, IObjectDefinitionFactory objectDefinitionFactory)
: base(registry)
{
Resolver = resolver;
this.objectDefinitionFactory = objectDefinitionFactory;
}
#endregion
#region Properties
/// <summary>
/// The <see cref="System.Xml.XmlResolver"/>to be used for parsing.
/// </summary>
public XmlResolver Resolver
{
get { return resolver; }
set { resolver = value; }
}
/// <summary>
/// Sets the IObjectDefinitionDocumentReader implementation to use, responsible for
/// the actual reading of the XML object definition document.stype of the document reader.
/// </summary>
/// <value>The type of the document reader.</value>
public Type DocumentReaderType
{
set
{
if (value == null || !typeof(IObjectDefinitionDocumentReader).IsAssignableFrom(value))
{
throw new ArgumentException(
"DocumentReaderType must be an implementation of the IObjectDefinitionReader interface.");
}
documentReaderType = value;
}
}
/// <summary>
/// Specify a <see cref="INamespaceParserResolver"/> to use. If none is specified a default
/// instance will be created by <see cref="CreateDefaultNamespaceParserResolver"/>
/// </summary>
internal INamespaceParserResolver NamespaceParserResolver
{
get
{
if (this.namespaceParserResolver == null)
{
this.namespaceParserResolver = CreateDefaultNamespaceParserResolver();
}
return this.namespaceParserResolver;
}
set
{
if (this.namespaceParserResolver != null)
{
throw new InvalidOperationException("NamespaceParserResolver is already set");
}
this.namespaceParserResolver = value;
}
}
/// <summary>
/// Specify a <see cref="IObjectDefinitionFactory"/> for creating instances of <see cref="AbstractObjectDefinition"/>.
/// </summary>
protected IObjectDefinitionFactory ObjectDefinitionFactory
{
get
{
return this.objectDefinitionFactory;
}
}
#endregion
#region Methods
/// <summary>
/// Load object definitions from the supplied XML <paramref name="resource"/>.
/// </summary>
/// <param name="resource">
/// The XML resource for the object definitions that are to be loaded.
/// </param>
/// <returns>
/// The number of object definitions that were loaded.
/// </returns>
/// <exception cref="Spring.Objects.ObjectsException">
/// In the case of loading or parsing errors.
/// </exception>
public override int LoadObjectDefinitions(IResource resource)
{
if (resource == null)
{
throw new ObjectDefinitionStoreException
("Resource cannot be null: expected an XML resource.");
}
#region Instrumentation
if (log.IsDebugEnabled)
{
log.Debug("Loading XML object definitions from " + resource);
}
#endregion
try
{
Stream stream = resource.InputStream;
if (stream == null)
{
throw new ObjectDefinitionStoreException(
"InputStream is null from Resource = [" + resource + "]");
}
try
{
return DoLoadObjectDefinitions(stream, resource);
}
finally
{
#region Close stream
try
{
stream.Close();
}
catch (IOException ex)
{
#region Instrumentation
if (log.IsWarnEnabled)
{
log.Warn("Could not close stream.", ex);
}
#endregion
}
#endregion
}
}
catch (IOException ex)
{
throw new ObjectDefinitionStoreException(
"IOException parsing XML document from " + resource.Description, ex);
}
}
/// <summary>
/// Actually load object definitions from the specified XML file.
/// </summary>
/// <param name="stream">The input stream to read from.</param>
/// <param name="resource">The resource for the XML data.</param>
/// <returns></returns>
protected virtual int DoLoadObjectDefinitions(Stream stream, IResource resource)
{
try
{
// create local copy of data
byte[] xmlData = IOUtils.ToByteArray(stream);
XmlDocument doc;
// loop until no unregistered, wellknown namespaces left
while (true)
{
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();
}
}
return RegisterObjectDefinitions(doc, resource);
}
catch (XmlException ex)
{
throw new ObjectDefinitionStoreException(resource.Description,
"Line " + ex.LineNumber + " in XML document from " +
resource + " is not well formed. " + ex.Message, ex);
}
catch (XmlSchemaException ex)
{
throw new ObjectDefinitionStoreException(resource.Description,
"Line " + ex.LineNumber + " in XML document from " +
resource + " violates the schema. " + ex.Message, ex);
}
catch (ObjectDefinitionStoreException)
{
throw;
}
catch (Exception ex)
{
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
}
/// <summary>
/// Validation callback for a validating XML reader.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="args">Any data pertinent to the event.</param>
private void HandleValidation(object sender, ValidationEventArgs args)
{
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)
{
return;
}
#endif
throw ex;
}
else
{
#region Instrumentation
if (log.IsWarnEnabled)
{
log.Warn(
"Ignored XML validation warning: " + args.Message,
args.Exception);
}
#endregion
}
}
/// <summary>
/// Register the object definitions contained in the given DOM document.
/// </summary>
/// <param name="doc">The DOM document.</param>
/// <param name="resource">
/// The original resource from where the <see cref="System.Xml.XmlDocument"/>
/// was read.
/// </param>
/// <returns>
/// The number of object definitions that were registered.
/// </returns>
/// <exception cref="Spring.Objects.ObjectsException">
/// In case of parsing errors.
/// </exception>
public int RegisterObjectDefinitions(
XmlDocument doc, IResource resource)
{
IObjectDefinitionDocumentReader documentReader = CreateObjectDefinitionDocumentReader();
//TODO make void return and get object count from registry.
int countBefore = Registry.ObjectDefinitionCount;
XmlReaderContext readerContext = CreateReaderContext(resource);
readerContext.NamespaceParserResolver = this.NamespaceParserResolver;
documentReader.RegisterObjectDefinitions(doc, readerContext);
return Registry.ObjectDefinitionCount - countBefore;
}
/// <summary>
/// Creates the <see cref="IObjectDefinitionDocumentReader"/> to use for actually
/// reading object definitions from an XML document.
/// </summary>
/// <remarks>Default implementation instantiates the specified <see cref="DocumentReaderType"/>
/// or <see cref="DefaultObjectDefinitionDocumentReader"/> if no reader type is specified.</remarks>
/// <returns></returns>
protected virtual IObjectDefinitionDocumentReader CreateObjectDefinitionDocumentReader()
{
if (documentReaderType == null)
{
return new DefaultObjectDefinitionDocumentReader();
}
return (IObjectDefinitionDocumentReader)ObjectUtils.InstantiateType(documentReaderType);
}
/// <summary>
/// Creates the <see cref="XmlReaderContext"/> to be passed along
/// during the object definition reading process.
/// </summary>
/// <param name="resource">The underlying <see cref="IResource"/> that is currently processed.</param>
/// <returns>A new <see cref="XmlReaderContext"/></returns>
protected virtual XmlReaderContext CreateReaderContext(IResource resource)
{
return new XmlReaderContext(resource, this, this.objectDefinitionFactory);
}
/// <summary>
/// Create a <see cref="INamespaceParserResolver"/> instance for handling custom namespaces.
/// </summary>
/// <remarks>
/// TODO (EE): make protected virtual, see remarks on <see cref="INamespaceParserResolver"/>
/// </remarks>
private INamespaceParserResolver CreateDefaultNamespaceParserResolver()
{
return new DefaultNamespaceHandlerResolver();
}
#endregion
}
}
|