// ========================================================
namespace Kerosene.ORM.WCF.Server
{
using Kerosene.ORM.Core;
using Kerosene.Tools;
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
// ====================================================
/// <summary>
/// Represents the server side of a WCF connection, or data context, with an arbitrary underlying database
/// managed by this proxy.
/// </summary>
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerSession, // Each client has its own session (proxy object)
IncludeExceptionDetailInFaults = true // To send back the exceptions to the client
)]
public abstract class KServerWCF : IKProxyWCF
{
bool _IsDisposed = false;
Guid _ProxyId = Guid.NewGuid();
IKLink _Link = null;
DeepObject _Package = null;
Dictionary<Guid, object> _Elements = new Dictionary<Guid, object>();
/// <summary>
/// Creates a new un-initialized proxy server.
/// </summary>
protected KServerWCF() { }
/// <summary>
/// Call-back method to create or obtain a link to serve the requests of the client of this server side
/// proxy instance.
/// </summary>
protected abstract IKLink CreateLink();
/// <summary>
/// Gets whether this instance has been disposed or not.
/// </summary>
public bool IsDisposed
{
get { return _IsDisposed; }
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
if (!IsDisposed) { OnDispose(true); GC.SuppressFinalize(this); }
}
~KServerWCF()
{
if (!IsDisposed) OnDispose(false);
}
protected virtual void OnDispose(bool disposing)
{
if (!IsDisposed && disposing)
{
if (_Link != null) OnProxyDisconnect();
_Link = null;
}
_IsDisposed = true;
}
/// <summary>
/// Returns the string representation of this instance.
/// </summary>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
if (IsDisposed) sb.Append("disposed::[");
sb.AppendFormat("{0}({1}: {2})",
GetType().EasyName(),
ProxyId.TagString(),
Link == null ? "-" : Link.ToString());
if (IsDisposed) sb.Append("]");
return sb.ToString();
}
/// <summary>
/// Gets the uid assigned to this instance.
/// </summary>
public Guid ProxyId
{
get { return _ProxyId; }
}
/// <summary>
/// Gets the link that represents the underlying database this server side proxy connects to on behalf of
/// the client it is connected with.
/// </summary>
public IKLink Link
{
get { return _Link; }
}
/// <summary>
/// Gets the package or arbitrary information the client used to connect to this server side proxy, or
/// null if no one was used, if this instance is not connected, or if it was disposed.
/// </summary>
public DeepObject Package
{
get { return _Package; }
}
/// <summary>
/// Invoked when connecting with a client.
/// </summary>
/// <param name="package">The optional package of arbitrary information sent by the client when it sent
/// the connection request, that this server side proxy can use to tailor the details of the services it
/// will provide to that client.</param>
public virtual Guid OnProxyConnect(DeepObject package = null)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
package == null ? "" : package.ToString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is already connected.", this));
_Package = package;
_Link = CreateLink(); if (_Link == null) throw new CannotCreateException(string.Format(
"Cannot create a new link for this '{0}'.", this));
return _ProxyId;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked when the client wants to disconnect from this server side proxy.
/// </summary>
public virtual void OnProxyDisconnect()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
foreach (var kvp in _Elements)
{
if (kvp.Value == null) continue;
if (kvp.Value is IDisposableExtended) { if (!((IDisposableExtended)kvp.Value).IsDisposed) ((IDisposable)kvp.Value).Dispose(); }
else if (kvp.Value is IDisposable) ((IDisposable)kvp.Value).Dispose();
}
_Elements.Clear();
if (_Link != null)
{
if (_Link.IsOpen) _Link.Close();
_Link.Dispose();
_Link = null;
}
_Package = null;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Gets whether this server side proxy is currently connected or not.
/// </summary>
/// <returns></returns>
public bool IsConnected()
{
return !(_Link == null);
}
/// <summary>
/// Invoked to open the actual server side connection with the arbitrary underlying database this server
/// creates to serve the requests of its client
/// <para>If the connection was already opened this method throws an exception.</para>
/// </summary>
public virtual void OnLinkOpen()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
_Link.Open();
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to close the server side connection with the arbitrary underlying database this server created
/// to serve the requests of its client.
/// <para>If the connection was not opened this method is merely ignored.</para>
/// </summary>
public virtual void OnLinkClose()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (_Link != null) _Link.Close();
_Link = null;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to get whether the server side connection with the arbitrary underlying database created by
/// this server is opened or not.
/// </summary>
public bool OnLinkIsOpen()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
return _Link.IsOpen;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to start a new transaction at the server side or, if it was active already, to increase its
/// nesting level.
/// </summary>
public virtual void OnTransactionStart()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
_Link.Transaction.Start();
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to commit the transaction at the server side or, if its nesting level is greter than one, to
/// decrease that nesting level.
/// <para>This method is merely ignored is the transaction was not active.</para>
/// </summary>
public virtual void OnTransactionCommit()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
_Link.Transaction.Commit();
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to abort the transaction at the server side regardless of its nesting level.
/// <para>This method is merely ignored is the transaction was not active.</para>
/// </summary>
public virtual void OnTransactionAbort()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
_Link.Transaction.Abort();
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to obtain the level of the transaction at the server side. A value of cero indicates that the
/// server side transaction is not active.
/// </summary>
public int OnTransactionLevel()
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
return _Link.Transaction.Level;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to create a new server side enumerator.
/// </summary>
/// <param name="text">The text of the command to be executed by the new enumerator.</param>
/// <param name="pars">the list of parameters to be used to execute the command of the new enumerator.</param>
public virtual Guid OnEnumeratorCreate(string text, IKParameterCollection pars)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3}, {4})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
text ?? "-",
pars == null ? "-" : pars.ToString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
var cmd = Link.Raw();
cmd.Set(text);
if(pars != null) foreach (var par in pars) cmd.Parameters.Add(par.Clone());
var obj = cmd.GetEnumerator();
var uid = Guid.NewGuid();
_Elements.Add(uid, obj);
return uid;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to dispose the server side enumerator.
/// </summary>
/// <param name="uid">The server side uid that identifies the enumerator.</param>
public virtual void OnEnumeratorDispose(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));
var obj = (IKEnumerator)temp;
var cmd = (IKCommandRaw)obj.Command;
obj.Dispose();
cmd.Dispose();
_Elements.Remove(uid);
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to execute the command associated with the server side enumerator. Returns the schema that
/// describes the structure and metadata of the objects to return.
/// </summary>
/// <param name="uid">The server side uid that identifies the enumerator.</param>
public virtual IKSchema OnEnumeratorStart(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));
var obj = (IKEnumerator)temp;
var schema = obj.OnReaderStart();
// MessageRpc disposes its 'ReturnParameter' if its implements 'IDisposable', which would cause
// the schema to be disposed. For this reason we return a clone, that at the same time will also
// be cloned at the client side to use there that local copy.
if (schema != null) schema = schema.Clone();
return schema;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to obtain the next record of the iteration, or null if no more records are available.
/// </summary>
/// <param name="uid">The server side uid that identifies the enumerator.</param>
public virtual IKRecord OnEnumeratorNext(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));
var obj = (IKEnumerator)temp;
var record = obj.OnReaderNext();
// MessageRpc disposes its 'ReturnParameter' if its implements 'IDisposable', which would cause
// the record to be disposed. For this reason we return a clone, that at the same time will also
// be cloned at the client side to use there that local copy.
if (record != null) record = record.Clone();
return record;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to reset the server side enumerator.
/// </summary>
/// <param name="uid">The server side uid that identifies the enumerator.</param>
public virtual void OnEnumeratorReset(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));
var obj = (IKEnumerator)temp;
obj.Reset();
#if DEBUG
// We force a collection at the server, in debug mode, to verify that collecting the objects created
// here at the server side do not interfere with the client side copies...
DebugHelper.IndentWriteLine("\n- GC Collector invoked ... {0}", _Elements.Count);
GC.Collect();
DebugHelper.Unindent();
#endif
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to create a new server side exectutor.
/// </summary>
/// <param name="text">The text of the command to be executed by the new exectutor.</param>
/// <param name="pars">the list of parameters to be used to execute the command of the new exectutor.</param>
public virtual Guid OnExecutorCreate(string text, IKParameterCollection pars)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3}, {4})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
text ?? "-",
pars == null ? "-" : pars.ToString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
var cmd = Link.Raw();
cmd.Set(text);
if (pars != null) foreach (var par in pars) cmd.Parameters.Add(par.Clone());
var obj = cmd.GetExecutor();
var uid = Guid.NewGuid();
_Elements.Add(uid, obj);
return uid;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to dispose the server side executor.
/// </summary>
/// <param name="uid">The server side uid that identifies the executor.</param>
public virtual void OnExecutorDispose(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Executor '{0}' not found.", uid.TagString()));
var obj = (IKExecutor)temp;
var cmd = (IKCommandRaw)obj.Command;
obj.Dispose();
cmd.Dispose();
_Elements.Remove(uid);
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
/// <summary>
/// Invoked to execute the server side executor, returning an integer that typically represents the number
/// of records that were affected by the command, among other possible meanings.
/// </summary>
/// <param name="uid">The server side uid that identifies the executor.</param>
public virtual int OnExecutorExecute(Guid uid)
{
DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
uid.TagString());
try
{
if (IsDisposed) throw new ObjectDisposedException(this.ToString());
if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));
object temp = null; if (!_Elements.TryGetValue(uid, out temp))
throw new NotFoundException(string.Format("Executor '{0}' not found.", uid.TagString()));
var obj = (IKExecutor)temp;
var r = obj.Execute();
return r;
}
catch (Exception e)
{
DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
throw e;
}
finally { DebugHelper.Unindent(); }
}
}
}
// ========================================================