SPRNET-923, fixed SaoExporter - WebServiceExporter is still TBD

This commit is contained in:
eeichinger
2008-10-09 21:29:56 +00:00
parent 08063accf6
commit 936b002304
2 changed files with 392 additions and 343 deletions

View File

@@ -1,7 +1,7 @@
#region License
#region License
/*
* Copyright 2002-2004 the original author or authors.
* Copyright 2002-2008 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.
@@ -14,349 +14,355 @@
* 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.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting;
using Spring.Core;
using Spring.Core.TypeResolution;
using Spring.Proxy;
using Spring.Context;
using Spring.Context.Support;
using Spring.Objects;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Support;
using Spring.Remoting.Support;
using Spring.Util;
#endregion
namespace Spring.Remoting
{
/// <summary>
/// Publishes an instance of an object under
/// a given url as a Server Activated Object (SAO).
/// </summary>
/// <remarks>
/// Object can be exported either as SingleCall or Singleton.
/// </remarks>
/// <author>Aleksandar Seovic</author>
/// <author>Mark Pollack</author>
/// <author>Bruno Baia</author>
public class SaoExporter : ConfigurableLifetime, IApplicationContextAware, IObjectFactoryAware, IInitializingObject, IDisposable
{
#region Logging
private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(SaoExporter));
#endregion
#region Fields
private string targetName;
private string applicationName;
private string serviceName;
private string[] interfaces;
private IApplicationContext applicationContext;
private IObjectFactory objectFactory;
private MarshalByRefObject remoteObject;
#endregion
#region Constructor(s) / Destructor
/// <summary>
/// Creates a new instance of the <see cref="SaoExporter"/> class.
/// </summary>
public SaoExporter()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the target object definition.
/// </summary>
public string TargetName
{
get { return targetName; }
set { targetName = value; }
}
/// <summary>
/// Gets or sets the name of the remote application.
/// </summary>
public string ApplicationName
{
get { return applicationName; }
set { applicationName = value; }
}
/// <summary>
/// Gets or sets the name of the exported remote service.
/// <remarks>
/// The name that will be used in the URI to refer to this service.
/// This will be of the form, tcp://host:port/ServiceName or
/// tcp://host:port/ApplicationName/ServiceName
/// </remarks>
/// </summary>
public string ServiceName
{
get { return serviceName; }
set { serviceName = value; }
}
/// <summary>
/// Gets or sets the list of interfaces whose methods should be exported.
/// </summary>
/// <remarks>
/// The default value of this property is all the interfaces
/// implemented or inherited by the target type.
/// </remarks>
/// <value>The interfaces to export.</value>
public string[] Interfaces
{
get { return interfaces; }
set { interfaces = value; }
}
#endregion
#region IApplicationContextAware Members
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting;
using Spring.Core.TypeResolution;
using Spring.Proxy;
using Spring.Objects.Factory;
using Spring.Remoting.Support;
using Spring.Util;
#endregion
namespace Spring.Remoting
{
/// <summary>
/// Publishes an instance of an object under
/// a given url as a Server Activated Object (SAO).
/// </summary>
/// <remarks>
/// Remoting servers exported by <see cref="SaoExporter"/> always correspond to <see cref="WellKnownObjectMode.Singleton"/>.
/// Objects can be exported either as SingleCall or Singleton by marking the exported object identified by
/// <see name="TargetName"/> as either singleton or prototype.
/// </remarks>
/// <author>Aleksandar Seovic</author>
/// <author>Mark Pollack</author>
/// <author>Bruno Baia</author>
/// <author>Erich Eichinger</author>
public class SaoExporter : ConfigurableLifetime, IObjectFactoryAware, IInitializingObject, IDisposable
{
#region Logging
private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger( typeof( SaoExporter ) );
#endregion
/// <summary>
/// Sets the <see cref="Spring.Context.IApplicationContext"/> that this
/// object runs in.
/// Holds EXPORTER_ID to SaoExporter instance mappings.
/// </summary>
/// <value></value>
private static readonly IDictionary s_activeExporters = new Hashtable();
///<summary>
/// Returns the target object instance exported by the SaoExporter identified by <see cref="EXPORTER_ID"/>.
///</summary>
///<param name="exporterId"></param>
///<returns></returns>
public static object GetTarget( string exporterId )
{
SaoExporter exporterInstance;
lock (s_activeExporters.SyncRoot)
{
exporterInstance = (SaoExporter)s_activeExporters[exporterId];
}
AssertUtils.ArgumentNotNull( exporterInstance, "exporterId", "Remoting Server object is not associated with any active SaoExporter" );
object target = exporterInstance.GetTargetInstance();
AssertUtils.ArgumentNotNull( target, "exporterId", string.Format( "Failed retrieving target object for SaoExporter ID {0}", exporterId ) );
return target;
}
#region Fields
private readonly string EXPORTER_ID = Guid.NewGuid().ToString();
private string targetName;
private string applicationName;
private string serviceName;
private string[] interfaces;
private IObjectFactory objectFactory;
private MarshalByRefObject remoteObject;
#endregion
#region Constructor(s) / Destructor
/// <summary>
/// Creates a new instance of the <see cref="SaoExporter"/> class.
/// </summary>
public SaoExporter()
{
lock (s_activeExporters.SyncRoot)
{
s_activeExporters[EXPORTER_ID] = this;
}
}
/// <summary>
/// Cleanup before GC
/// </summary>
~SaoExporter()
{
Dispose( false );
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the target object definition.
/// </summary>
public string TargetName
{
get { return targetName; }
set { targetName = value; }
}
/// <summary>
/// Gets or sets the name of the remote application.
/// </summary>
public string ApplicationName
{
get { return applicationName; }
set { applicationName = value; }
}
/// <summary>
/// Gets or sets the name of the exported remote service.
/// <remarks>
/// <p>
/// Normally this call will be used to initialize the object.
/// </p>
/// <p>
/// Invoked after population of normal object properties but before an
/// init callback such as
/// <see cref="Spring.Objects.Factory.IInitializingObject"/>'s
/// <see cref="Spring.Objects.Factory.IInitializingObject.AfterPropertiesSet"/>
/// or a custom init-method. Invoked after the setting of any
/// <see cref="Spring.Context.IResourceLoaderAware"/>'s
/// <see cref="Spring.Context.IResourceLoaderAware.ResourceLoader"/>
/// property.
/// </p>
/// The name that will be used in the URI to refer to this service.
/// This will be of the form, tcp://host:port/ServiceName or
/// tcp://host:port/ApplicationName/ServiceName
/// </remarks>
/// <exception cref="Spring.Context.ApplicationContextException">
/// In the case of application context initialization errors.
/// </exception>
/// <exception cref="Spring.Objects.ObjectsException">
/// If thrown by any application context methods.
/// </exception>
/// <exception cref="Spring.Objects.Factory.ObjectInitializationException"/>
public IApplicationContext ApplicationContext
{
set { applicationContext = value; }
}
#endregion
#region IObjectFactoryAware Members
/// <summary>
/// Sets object factory to use.
/// </summary>
public IObjectFactory ObjectFactory
{
set { objectFactory = value; }
}
#endregion
#region IInitializingObject Members
/// <summary>
/// Publish the object
/// </summary>
public void AfterPropertiesSet()
{
ValidateConfiguration();
Export();
}
#endregion
#region IDisposable Members
/// <summary>
/// Disconnect the remote object from the registered remoting channels.
/// </summary>
public void Dispose()
{
RemotingServices.Disconnect(remoteObject);
}
#endregion
#region Private Methods
private void ValidateConfiguration()
{
if (TargetName == null)
{
throw new ArgumentException("The TargetName property is required.");
}
if (ServiceName == null)
{
throw new ArgumentException("The ServiceName property is required.");
}
}
private void Export()
{
IProxyTypeBuilder builder = new SaoRemoteObjectProxyTypeBuilder(applicationContext.Name, targetName, this);
builder.TargetType = this.objectFactory.GetType(targetName);
if (interfaces != null && interfaces.Length > 0)
{
builder.Interfaces = TypeResolutionUtils.ResolveInterfaceArray(interfaces);
}
ConstructorInfo proxyConstructor =
builder.BuildProxyType().GetConstructor(Type.EmptyTypes);
string objectUri = (applicationName != null ? applicationName + "/" + serviceName : serviceName);
remoteObject = (MarshalByRefObject) proxyConstructor.Invoke(ObjectUtils.EmptyObjects);
RemotingServices.Marshal(remoteObject, objectUri);
#region Instrumentation
if (LOG.IsDebugEnabled)
{
LOG.Debug(String.Format("Target '{0}' exported as '{1}'.", targetName, objectUri));
}
#endregion
}
#endregion
#region SaoRemoteObjectProxyTypeBuilder inner class definition
/// <summary>
/// Builds a proxy type based on <see cref="BaseRemoteObject"/> to wrap a target object
/// that is intended to be remotable.
/// </summary>
/// <remarks>
/// The wrapped target object is retrieved by name from the IoC container.
/// </remarks>
private sealed class SaoRemoteObjectProxyTypeBuilder : RemoteObjectProxyTypeBuilder
{
#region Fields
private static readonly MethodInfo TimeSpan_FromTicks =
typeof(TimeSpan).GetMethod("FromTicks", BindingFlags.Public | BindingFlags.Static);
private static readonly MethodInfo ContextRegistry_GetContext =
typeof(ContextRegistry).GetMethod("GetContext", new Type[1] { typeof(string) });
private static readonly MethodInfo IObjectFactory_GetObject =
typeof(IObjectFactory).GetMethod("GetObject", new Type[1] { typeof(string) });
private string contextName;
private string targetObjectName;
#endregion
#region Constructor(s) / Destructor
/// <summary>
/// Creates a new instance of the
/// <see cref="SaoRemoteObjectProxyTypeBuilder"/> class.
/// </summary>
/// <param name="contextName">
/// The name of the target object definition.
/// </param>
/// <param name="targetObjectName">
/// The name of the application context that this object runs in.
/// </param>
/// <param name="lifetime">
/// The lifetime properties to be applied to the target object.
/// </param>
public SaoRemoteObjectProxyTypeBuilder(string contextName, string targetObjectName, ILifetime lifetime)
: base(lifetime)
{
this.contextName = contextName;
this.targetObjectName = targetObjectName;
Name = "SaoRemoteObjectProxy";
BaseType = typeof(BaseRemoteObject);
}
#endregion
#region IProxyTypeGenerator Members
/// <summary>
/// Generates the IL instructions that pushes
/// the target instance on which calls should be delegated to.
/// </summary>
/// <param name="il">The IL generator to use.</param>
public override void PushTarget(ILGenerator il)
{
il.Emit(OpCodes.Ldstr, this.contextName);
il.Emit(OpCodes.Call, ContextRegistry_GetContext);
il.Emit(OpCodes.Ldstr, this.targetObjectName);
il.Emit(OpCodes.Callvirt, IObjectFactory_GetObject);
}
#endregion
#region Protected Methods
/// <summary>
/// Implements constructors for the proxy class.
/// </summary>
/// <param name="builder">
/// The <see cref="System.Reflection.Emit.TypeBuilder"/> to use.
/// </param>
protected override void ImplementConstructors(TypeBuilder builder)
{
MethodAttributes attributes = MethodAttributes.Public |
MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName;
ConstructorBuilder cb = builder.DefineConstructor(attributes,
CallingConventions.Standard, Type.EmptyTypes);
ILGenerator il = cb.GetILGenerator();
GenerateRemoteObjectLifetimeInitialization(il);
il.Emit(OpCodes.Ret);
}
/// <summary>
/// Deaclares a field that holds the target object instance.
/// </summary>
/// <param name="builder">
/// The <see cref="System.Type"/> builder to use for code generation.
/// </param>
protected override void DeclareTargetInstanceField(TypeBuilder builder)
{
}
#endregion
}
#endregion
}
/// </summary>
public string ServiceName
{
get { return serviceName; }
set { serviceName = value; }
}
/// <summary>
/// Gets or sets the list of interfaces whose methods should be exported.
/// </summary>
/// <remarks>
/// The default value of this property is all the interfaces
/// implemented or inherited by the target type.
/// </remarks>
/// <value>The interfaces to export.</value>
public string[] Interfaces
{
get { return interfaces; }
set { interfaces = value; }
}
#endregion
#region IObjectFactoryAware Members
/// <summary>
/// Sets object factory to use.
/// </summary>
public IObjectFactory ObjectFactory
{
set { objectFactory = value; }
}
#endregion
#region IInitializingObject Members
/// <summary>
/// Publish the object
/// </summary>
public void AfterPropertiesSet()
{
ValidateConfiguration();
Export();
}
#endregion
#region IDisposable Members
/// <summary>
/// Disconnect the remote object from the registered remoting channels.
/// </summary>
public void Dispose()
{
GC.SuppressFinalize( this );
Dispose( true );
}
/// <summary>
/// Stops exporting the object identified by <see cref="TargetName"/>.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose( bool disposing )
{
if (disposing)
{
RemotingServices.Disconnect( remoteObject );
lock (s_activeExporters.SyncRoot)
{
s_activeExporters.Remove( this.EXPORTER_ID );
}
remoteObject = null;
objectFactory = null;
}
}
#endregion
#region Private Methods
private object GetTargetInstance()
{
return objectFactory.GetObject( targetName );
}
private void ValidateConfiguration()
{
if (TargetName == null)
{
throw new ArgumentException( "The TargetName property is required." );
}
if (ServiceName == null)
{
throw new ArgumentException( "The ServiceName property is required." );
}
}
private void Export()
{
if (remoteObject != null)
{
throw new InvalidOperationException("object is already exported");
}
IProxyTypeBuilder builder = new SaoRemoteObjectProxyTypeBuilder( this );
builder.TargetType = this.objectFactory.GetType( targetName );
if (interfaces != null && interfaces.Length > 0)
{
builder.Interfaces = TypeResolutionUtils.ResolveInterfaceArray( interfaces );
}
Type proxyType = builder.BuildProxyType();
string objectUri = (applicationName != null ? applicationName + "/" + serviceName : serviceName);
ConstructorInfo proxyConstructor = proxyType.GetConstructor( Type.EmptyTypes );
remoteObject = (MarshalByRefObject)proxyConstructor.Invoke( ObjectUtils.EmptyObjects );
RemotingServices.Marshal( remoteObject, objectUri );
#region Instrumentation
if (LOG.IsDebugEnabled)
{
LOG.Debug( String.Format( "Target '{0}' exported as '{1}'.", targetName, objectUri ) );
}
#endregion
}
#endregion
#region SaoRemoteObjectProxyTypeBuilder inner class definition
/// <summary>
/// Builds a proxy type based on <see cref="BaseRemoteObject"/> to wrap a target object
/// that is intended to be remotable.
/// </summary>
/// <remarks>
/// The wrapped target object is retrieved by name from the IoC container.
/// </remarks>
private sealed class SaoRemoteObjectProxyTypeBuilder : RemoteObjectProxyTypeBuilder
{
#region Fields
private static readonly MethodInfo SaoExporter_GetTargetInstance = typeof( SaoExporter ).GetMethod( "GetTarget", new Type[] { typeof( string ) } );
private SaoExporter _saoExporter;
#endregion
#region Constructor(s) / Destructor
/// <summary>
/// Creates a new instance of the
/// <see cref="SaoRemoteObjectProxyTypeBuilder"/> class.
/// </summary>
/// <param name="saoExporter">
/// The exporter to be associated with the proxy.
/// </param>
public SaoRemoteObjectProxyTypeBuilder( SaoExporter saoExporter )
: base( saoExporter )
{
this._saoExporter = saoExporter;
Name = "SaoRemoteObjectProxy";
BaseType = typeof( BaseRemoteObject );
}
#endregion
#region IProxyTypeGenerator Members
/// <summary>
/// Generates the IL instructions that pushes
/// the target instance on which calls should be delegated to.
/// </summary>
/// <param name="il">The IL generator to use.</param>
public override void PushTarget( ILGenerator il )
{
il.Emit( OpCodes.Ldstr, this._saoExporter.EXPORTER_ID );
il.Emit( OpCodes.Call, SaoExporter_GetTargetInstance );
}
#endregion
#region Protected Methods
/// <summary>
/// Implements constructors for the proxy class.
/// </summary>
/// <param name="builder">
/// The <see cref="System.Reflection.Emit.TypeBuilder"/> to use.
/// </param>
protected override void ImplementConstructors( TypeBuilder builder )
{
MethodAttributes attributes = MethodAttributes.Public |
MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName;
ConstructorBuilder cb = builder.DefineConstructor( attributes,
CallingConventions.Standard, Type.EmptyTypes );
ILGenerator il = cb.GetILGenerator();
GenerateRemoteObjectLifetimeInitialization( il );
il.Emit( OpCodes.Ret );
}
/// <summary>
/// Deaclares a field that holds the target object instance.
/// </summary>
/// <param name="builder">
/// The <see cref="System.Type"/> builder to use for code generation.
/// </param>
protected override void DeclareTargetInstanceField( TypeBuilder builder )
{
}
#endregion
}
#endregion
}
}

View File

@@ -22,6 +22,8 @@
using System;
using NUnit.Framework;
using Spring.Objects;
using Spring.Objects.Factory.Support;
#endregion
@@ -42,5 +44,46 @@ namespace Spring.Remoting
SaoExporter exp = new SaoExporter();
exp.AfterPropertiesSet();
}
[Test]
public void ExportSingleton()
{
using(DefaultListableObjectFactory of = new DefaultListableObjectFactory())
{
of.RegisterSingleton("simpleCounter", new SimpleCounter());
SaoExporter saoExporter = new SaoExporter();
saoExporter.ObjectFactory = of;
saoExporter.TargetName = "simpleCounter";
saoExporter.ServiceName = "RemotedSaoSingletonCounter";
saoExporter.AfterPropertiesSet();
of.RegisterSingleton("simpleCounterExporter", saoExporter); // also tests SaoExporter.Dispose()!
ISimpleCounter client = (ISimpleCounter) Activator.GetObject(typeof(ISimpleCounter), "tcp://localhost:8005/RemotedSaoSingletonCounter" );
client.Count();
client.Count();
Assert.AreEqual(2, client.Counter);
}
}
[Test]
public void ExportSingleCall()
{
using(DefaultListableObjectFactory of = new DefaultListableObjectFactory())
{
of.RegisterObjectDefinition("simpleCounter", new RootObjectDefinition(typeof(SimpleCounter), false));
SaoExporter saoExporter = new SaoExporter();
saoExporter.ObjectFactory = of;
saoExporter.TargetName = "simpleCounter";
saoExporter.ServiceName = "RemotedSaoSingleCallCounter";
saoExporter.AfterPropertiesSet();
of.RegisterSingleton("simpleCounterExporter", saoExporter); // also tests SaoExporter.Dispose()!
ISimpleCounter client = (ISimpleCounter) Activator.GetObject(typeof(ISimpleCounter), "tcp://localhost:8005/RemotedSaoSingleCallCounter" );
client.Count();
client.Count();
Assert.AreEqual(0, client.Counter);
}
}
}
}