From 936b002304c8cae189ac29ba125a857286eccf1a Mon Sep 17 00:00:00 2001 From: eeichinger Date: Thu, 9 Oct 2008 21:29:56 +0000 Subject: [PATCH] SPRNET-923, fixed SaoExporter - WebServiceExporter is still TBD --- .../Spring.Services/Remoting/SaoExporter.cs | 692 +++++++++--------- .../Remoting/SaoExporterTests.cs | 43 ++ 2 files changed, 392 insertions(+), 343 deletions(-) diff --git a/src/Spring/Spring.Services/Remoting/SaoExporter.cs b/src/Spring/Spring.Services/Remoting/SaoExporter.cs index fa760777..18a74089 100644 --- a/src/Spring/Spring.Services/Remoting/SaoExporter.cs +++ b/src/Spring/Spring.Services/Remoting/SaoExporter.cs @@ -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 -{ - /// - /// Publishes an instance of an object under - /// a given url as a Server Activated Object (SAO). - /// - /// - /// Object can be exported either as SingleCall or Singleton. - /// - /// Aleksandar Seovic - /// Mark Pollack - /// Bruno Baia - 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 - - /// - /// Creates a new instance of the class. - /// - public SaoExporter() - { - } - - #endregion - - #region Properties - - /// - /// Gets or sets the name of the target object definition. - /// - public string TargetName - { - get { return targetName; } - set { targetName = value; } - } - - /// - /// Gets or sets the name of the remote application. - /// - public string ApplicationName - { - get { return applicationName; } - set { applicationName = value; } - } - - /// - /// Gets or sets the name of the exported remote service. - /// - /// 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 - /// - /// - public string ServiceName - { - get { return serviceName; } - set { serviceName = value; } - } - - /// - /// Gets or sets the list of interfaces whose methods should be exported. - /// - /// - /// The default value of this property is all the interfaces - /// implemented or inherited by the target type. - /// - /// The interfaces to export. - 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 +{ + /// + /// Publishes an instance of an object under + /// a given url as a Server Activated Object (SAO). + /// + /// + /// Remoting servers exported by always correspond to . + /// Objects can be exported either as SingleCall or Singleton by marking the exported object identified by + /// as either singleton or prototype. + /// + /// Aleksandar Seovic + /// Mark Pollack + /// Bruno Baia + /// Erich Eichinger + public class SaoExporter : ConfigurableLifetime, IObjectFactoryAware, IInitializingObject, IDisposable + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger( typeof( SaoExporter ) ); + + #endregion /// - /// Sets the that this - /// object runs in. + /// Holds EXPORTER_ID to SaoExporter instance mappings. /// - /// + private static readonly IDictionary s_activeExporters = new Hashtable(); + + /// + /// Returns the target object instance exported by the SaoExporter identified by . + /// + /// + /// + 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 + + /// + /// Creates a new instance of the class. + /// + public SaoExporter() + { + lock (s_activeExporters.SyncRoot) + { + s_activeExporters[EXPORTER_ID] = this; + } + } + + /// + /// Cleanup before GC + /// + ~SaoExporter() + { + Dispose( false ); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the name of the target object definition. + /// + public string TargetName + { + get { return targetName; } + set { targetName = value; } + } + + /// + /// Gets or sets the name of the remote application. + /// + public string ApplicationName + { + get { return applicationName; } + set { applicationName = value; } + } + + /// + /// Gets or sets the name of the exported remote service. /// - ///

- /// Normally this call will be used to initialize the object. - ///

- ///

- /// Invoked after population of normal object properties but before an - /// init callback such as - /// 's - /// - /// or a custom init-method. Invoked after the setting of any - /// 's - /// - /// property. - ///

+ /// 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 ///
- /// - /// In the case of application context initialization errors. - /// - /// - /// If thrown by any application context methods. - /// - /// - public IApplicationContext ApplicationContext - { - set { applicationContext = value; } - } - - #endregion - - #region IObjectFactoryAware Members - - /// - /// Sets object factory to use. - /// - public IObjectFactory ObjectFactory - { - set { objectFactory = value; } - } - - #endregion - - #region IInitializingObject Members - - /// - /// Publish the object - /// - public void AfterPropertiesSet() - { - ValidateConfiguration(); - Export(); - } - - #endregion - - #region IDisposable Members - - /// - /// Disconnect the remote object from the registered remoting channels. - /// - 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 - - /// - /// Builds a proxy type based on to wrap a target object - /// that is intended to be remotable. - /// - /// - /// The wrapped target object is retrieved by name from the IoC container. - /// - 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 - - /// - /// Creates a new instance of the - /// class. - /// - /// - /// The name of the target object definition. - /// - /// - /// The name of the application context that this object runs in. - /// - /// - /// The lifetime properties to be applied to the target object. - /// - 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 - - /// - /// Generates the IL instructions that pushes - /// the target instance on which calls should be delegated to. - /// - /// The IL generator to use. - 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 - - /// - /// Implements constructors for the proxy class. - /// - /// - /// The to use. - /// - 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); - } - - /// - /// Deaclares a field that holds the target object instance. - /// - /// - /// The builder to use for code generation. - /// - protected override void DeclareTargetInstanceField(TypeBuilder builder) - { - } - - #endregion - } - - #endregion - } + ///
+ public string ServiceName + { + get { return serviceName; } + set { serviceName = value; } + } + + /// + /// Gets or sets the list of interfaces whose methods should be exported. + /// + /// + /// The default value of this property is all the interfaces + /// implemented or inherited by the target type. + /// + /// The interfaces to export. + public string[] Interfaces + { + get { return interfaces; } + set { interfaces = value; } + } + + #endregion + + #region IObjectFactoryAware Members + + /// + /// Sets object factory to use. + /// + public IObjectFactory ObjectFactory + { + set { objectFactory = value; } + } + + #endregion + + #region IInitializingObject Members + + /// + /// Publish the object + /// + public void AfterPropertiesSet() + { + ValidateConfiguration(); + Export(); + } + + #endregion + + #region IDisposable Members + + /// + /// Disconnect the remote object from the registered remoting channels. + /// + public void Dispose() + { + GC.SuppressFinalize( this ); + Dispose( true ); + } + + /// + /// Stops exporting the object identified by . + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + 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 + + /// + /// Builds a proxy type based on to wrap a target object + /// that is intended to be remotable. + /// + /// + /// The wrapped target object is retrieved by name from the IoC container. + /// + 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 + + /// + /// Creates a new instance of the + /// class. + /// + /// + /// The exporter to be associated with the proxy. + /// + public SaoRemoteObjectProxyTypeBuilder( SaoExporter saoExporter ) + : base( saoExporter ) + { + this._saoExporter = saoExporter; + + Name = "SaoRemoteObjectProxy"; + BaseType = typeof( BaseRemoteObject ); + } + + #endregion + + #region IProxyTypeGenerator Members + + /// + /// Generates the IL instructions that pushes + /// the target instance on which calls should be delegated to. + /// + /// The IL generator to use. + public override void PushTarget( ILGenerator il ) + { + il.Emit( OpCodes.Ldstr, this._saoExporter.EXPORTER_ID ); + il.Emit( OpCodes.Call, SaoExporter_GetTargetInstance ); + } + + #endregion + + #region Protected Methods + + /// + /// Implements constructors for the proxy class. + /// + /// + /// The to use. + /// + 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 ); + } + + /// + /// Deaclares a field that holds the target object instance. + /// + /// + /// The builder to use for code generation. + /// + protected override void DeclareTargetInstanceField( TypeBuilder builder ) + { + } + + #endregion + } + + #endregion + } } \ No newline at end of file diff --git a/test/Spring/Spring.Services.Tests/Remoting/SaoExporterTests.cs b/test/Spring/Spring.Services.Tests/Remoting/SaoExporterTests.cs index 433d9510..2cac107c 100644 --- a/test/Spring/Spring.Services.Tests/Remoting/SaoExporterTests.cs +++ b/test/Spring/Spring.Services.Tests/Remoting/SaoExporterTests.cs @@ -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); + } + } } }