#region License

/*
 * Copyright 2002-2004 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.Runtime.Remoting;

using Spring.Context;
using Spring.Core;
using Spring.Objects;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Support;
using Spring.Remoting.Support;
using Spring.Util;

#endregion

namespace Spring.Remoting
{
	/// <summary>
	/// Registers an object type on the server
	/// as a Client Activated Object (CAO).
	/// </summary>
	/// <author>Aleksandar Seovic</author>
	/// <author>Mark Pollack</author>
	/// <author>Bruno Baia</author>
	public class CaoExporter : ConfigurableLifetime, IApplicationContextAware, IObjectFactoryAware, IInitializingObject, IDisposable
	{
		#region Logging

		private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(CaoExporter));

		#endregion

		#region Fields

		private string targetName;
        private string[] interfaces;

		private IApplicationContext applicationContext;
		private AbstractObjectFactory objectFactory;

		private CaoRemoteFactory remoteFactory;

		#endregion

		#region Constructor(s) / Destructor

		/// <summary>
		/// Creates a new instance of the <see cref="CaoExporter"/> class.
		/// </summary>
		public CaoExporter()
		{
		}

		#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 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

        /// <summary>
        /// Sets the <see cref="Spring.Context.IApplicationContext"/> that this
        /// object runs in.
        /// </summary>
        /// <value></value>
        /// <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>
        /// </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 = (AbstractObjectFactory) 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(remoteFactory);
		}

		#endregion

		#region Private Methods

		private void ValidateConfiguration()
		{
			if (TargetName == null)
			{
				throw new ArgumentException("The TargetName property is required.");
			}
		}

		private void Export()
		{
			remoteFactory = new CaoRemoteFactory(this, targetName, interfaces, objectFactory);

			RemotingServices.Marshal(remoteFactory, targetName);

			#region Instrumentation

			if (LOG.IsDebugEnabled)
			{
				LOG.Debug(String.Format("Target '{0}' registered.", targetName));
			}

			#endregion
		}

		#endregion

        #region BaseCao inner class definition

        /// <summary>
        /// This class extends <see cref="Spring.Remoting.Support.BaseRemoteObject"/> to allow CAOs
        /// to be disconnect from the client.
        /// </summary>
        public abstract class BaseCao : BaseRemoteObject, IDisposable
        {
            #region IDisposable Members

            void IDisposable.Dispose()
            {
                RemotingServices.Disconnect(this);
            }

            #endregion
        }

        #endregion

        #region CaoRemoteFactory inner class definition

        private sealed class CaoRemoteFactory : MarshalByRefObject, ICaoRemoteFactory
		{
			#region Fields

			private AbstractObjectFactory objectFactory;
			private string targetName;
            private RemoteObjectFactory remoteObjectFactory;

			#endregion

			#region Constructor(s) / Destructor

			/// <summary>
			/// Create a new instance of the RemoteFactory.
			/// </summary>
            public CaoRemoteFactory(ILifetime lifetime, string targetName, 
                string[] interfaces, AbstractObjectFactory objectFactory)
			{
				this.targetName = targetName;
				this.objectFactory = objectFactory;

                this.remoteObjectFactory = new RemoteObjectFactory();
                this.remoteObjectFactory.BaseType = typeof(BaseCao);
                this.remoteObjectFactory.Interfaces = interfaces;
                this.remoteObjectFactory.Infinite = lifetime.Infinite;
                this.remoteObjectFactory.InitialLeaseTime = lifetime.InitialLeaseTime;
                this.remoteObjectFactory.RenewOnCallTime = lifetime.RenewOnCallTime;
                this.remoteObjectFactory.SponsorshipTimeout = lifetime.SponsorshipTimeout;
			}

			#endregion

			#region Membres de ICaoRemoteFactory

			/// <summary>
			/// Returns the CAO proxy.
			/// </summary>
			/// <returns>The remote object.</returns>
			public object GetObject()
			{
				remoteObjectFactory.Target = objectFactory.GetObject(targetName);
                
                return remoteObjectFactory.GetObject();
			}

			/// <summary>
			/// Returns the CAO proxy using the
			/// argument list to call the constructor. 
			/// </summary>
			/// <remarks>
			/// The matching of arguments to call the constructor is done
			/// by type. The alternative ways, by index and by constructor
			/// name are not supported.
			/// </remarks>
			/// <param name="constructorArguments">Constructor 
			/// arguments used to create the object.</param>
			/// <returns>The remote object.</returns>
			public object GetObject(object[] constructorArguments)
			{
				RootObjectDefinition mergedObjectDefinition = objectFactory.GetMergedObjectDefinition(targetName, false);

				if (typeof(IFactoryObject).IsAssignableFrom(mergedObjectDefinition.ObjectType))
				{
					throw new NotSupportedException(
                        "Client activated objects with constructor arguments is not supported with IFactoryObject implementations.");
				}

                remoteObjectFactory.Target = objectFactory.GetObject(targetName, constructorArguments);

                return remoteObjectFactory.GetObject();
			}

			#endregion

            #region Overrided Methods

            /// <summary>
            /// Set infinite lifetime.
            /// </summary>
            public override object InitializeLifetimeService()
            {
                return null;
            }

            #endregion
        }

		#endregion
	}
}