// ********************************************************************** // // Copyright (c) 2003-2016 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** using System; using System.Diagnostics; using System.Threading; using System.Collections.Generic; namespace Glacier2 { /// /// A helper class for using Glacier2 with GUI applications. /// public class SessionHelper { private class ConnectionCallbackI : Ice.ConnectionCallback { internal ConnectionCallbackI(SessionHelper sessionHelper) { _sessionHelper = sessionHelper; } public void heartbeat(Ice.Connection con) { } public void closed(Ice.Connection con) { _sessionHelper.destroy(); } private readonly SessionHelper _sessionHelper; } /// /// Creates a Glacier2 session. /// /// The callback for notifications about session /// establishment. /// The Ice.InitializationData for initializing /// the communicator. /// The stringified Ice.RouterFinder proxy. /// True if the session should create an object adapter for receiving callbacks. public SessionHelper(SessionCallback callback, Ice.InitializationData initData, string finderStr, bool useCallbacks) { _callback = callback; _initData = initData; _finderStr = finderStr; _useCallbacks = useCallbacks; } /// /// Destroys the Glacier2 session. /// /// Once the session has been destroyed, SessionCallback.disconnected is /// called on the associated callback object. /// public void destroy() { lock(this) { if(_destroy) { return; } _destroy = true; if(!_connected) { // // In this case a connecting session is being destroyed. // We destroy the communicator to trigger the immediate // failure of the connection establishment. // Thread t1 = new Thread(new ThreadStart(destroyCommunicator)); t1.Start(); return; } _session = null; _connected = false; // // Run destroyInternal in a thread because it makes remote invocations. // Thread t2 = new Thread(new ThreadStart(destroyInternal)); t2.Start(); } } /// /// Returns the session's communicator object. /// /// The communicator. public Ice.Communicator communicator() { lock(this) { return _communicator; } } /// /// Returns the category to be used in the identities of all of /// the client's callback objects. Clients must use this category /// for the router to forward callback requests to the intended /// client. /// /// The category. Throws SessionNotExistException /// No session exists public string categoryForClient() { lock(this) { if(_router == null) { throw new SessionNotExistException(); } return _category; } } /// /// Adds a servant to the callback object adapter's Active Servant /// Map with a UUID. /// /// The servant to add. /// The proxy for the servant. Throws SessionNotExistException /// if no session exists. public Ice.ObjectPrx addWithUUID(Ice.Object servant) { lock(this) { if(_router == null) { throw new SessionNotExistException(); } return internalObjectAdapter().add(servant, new Ice.Identity(Guid.NewGuid().ToString(), _category)); } } /// /// Returns the Glacier2 session proxy. If the session hasn't been /// established yet, or the session has already been destroyed, /// throws SessionNotExistException. /// /// The session proxy, or throws SessionNotExistException /// if no session exists. public Glacier2.SessionPrx session() { lock(this) { if(_session == null) { throw new SessionNotExistException(); } return _session; } } /// /// Returns true if there is an active session, otherwise returns false. /// /// true if session exists or false if no session exists. public bool isConnected() { lock(this) { return _connected; } } /// /// Returns an object adapter for callback objects, creating it if necessary. /// /// The object adapter. Throws SessionNotExistException /// if no session exists. public Ice.ObjectAdapter objectAdapter() { return internalObjectAdapter(); } private Ice.ObjectAdapter internalObjectAdapter() { lock(this) { if(_router == null) { throw new SessionNotExistException(); } if(!_useCallbacks) { throw new Ice.InitializationException( "Object adapter not available, call SessionFactoryHelper.setUseCallbacks(true)"); } return _adapter; } } /// /// Connects to the Glacier2 router using the associated SSL credentials. /// /// Once the connection is established, SessionCallback.connected is called on /// the callback object; upon failure, SessionCallback.exception is called with /// the exception. /// /// The request context to use when creating the session. public void connect(Dictionary context) { lock(this) { connectImpl((RouterPrx router) => { return router.createSessionFromSecureConnection(context); }); } } /// /// Connects a Glacier2 session using user name and password credentials. /// /// Once the connection is established, SessionCallback.connected is called on the callback object; /// upon failure SessionCallback.exception is called with the exception. /// /// The user name. /// The password. /// The request context to use when creating the session. public void connect(string username, string password, Dictionary context) { lock(this) { connectImpl((RouterPrx router) => { return router.createSession(username, password, context); }); } } private void connected(RouterPrx router, SessionPrx session) { // // Remote invocation should be done without acquiring a mutex lock. // Debug.Assert(router != null); Ice.Connection conn = router.ice_getCachedConnection(); string category = router.getCategoryForClient(); int acmTimeout = 0; try { acmTimeout = router.getACMTimeout(); } catch(Ice.OperationNotExistException) { } if(acmTimeout <= 0) { acmTimeout = (int)router.getSessionTimeout(); } // // We create the callback object adapter here because createObjectAdapter internally // makes synchronous RPCs to the router. We can't create the OA on-demand when the // client calls objectAdapter() or addWithUUID() because they can be called from the // GUI thread. // if(_useCallbacks) { Debug.Assert(_adapter == null); _adapter = _communicator.createObjectAdapterWithRouter("", router); _adapter.activate(); } lock(this) { _router = router; if(_destroy) { // // Run destroyInternal in a thread because it makes remote invocations. // Thread t = new Thread(new ThreadStart(destroyInternal)); t.Start(); return; } // // Cache the category. // _category = category; // // Assign the session after _destroy is checked. // _session = session; _connected = true; if(acmTimeout > 0) { Ice.Connection connection = _router.ice_getCachedConnection(); Debug.Assert(connection != null); connection.setACM(acmTimeout, Ice.Util.None, Ice.ACMHeartbeat.HeartbeatAlways); connection.setCallback(new ConnectionCallbackI(this)); } } dispatchCallback(() => { try { _callback.connected(this); } catch(Glacier2.SessionNotExistException) { destroy(); } }, conn); } private void destroyInternal() { Glacier2.RouterPrx router; Ice.Communicator communicator; lock(this) { Debug.Assert(_destroy); if(_router == null) { return; } router = _router; _router = null; communicator = _communicator; Debug.Assert(communicator != null); } try { router.destroySession(); } catch(Ice.ConnectionLostException) { // // Expected if another thread invoked on an object from the session concurrently. // } catch(SessionNotExistException) { // // This can also occur. // } catch(Exception e) { // // Not expected. // communicator.getLogger().warning("SessionHelper: unexpected exception when destroying the session:\n" + e); } try { communicator.destroy(); } catch(Exception) { } // Notify the callback that the session is gone. dispatchCallback(() => { _callback.disconnected(this); }, null); } private void destroyCommunicator() { Ice.Communicator communicator; lock(this) { communicator = _communicator; } try { communicator.destroy(); } catch(Exception) { } } delegate Glacier2.SessionPrx ConnectStrategy(Glacier2.RouterPrx router); private void connectImpl(ConnectStrategy factory) { Debug.Assert(!_destroy); try { _communicator = Ice.Util.initialize(_initData); } catch(Ice.LocalException ex) { _destroy = true; new Thread( new ThreadStart(() => { dispatchCallback(() => { _callback.connectFailed(this, ex); }, null); })).Start(); return; } Ice.RouterFinderPrx finder = Ice.RouterFinderPrxHelper.uncheckedCast(_communicator.stringToProxy(_finderStr)); new Thread(new ThreadStart(() => { if(_communicator.getDefaultRouter() == null) { try { _communicator.setDefaultRouter(finder.getRouter()); } catch(Ice.CommunicatorDestroyedException ex) { dispatchCallback(() => { _callback.connectFailed(this, ex); }, null); return; } catch(Exception) { // // In case of error getting router identity from RouterFinder use default identity. // Ice.Identity ident = new Ice.Identity("router", "Glacier2"); _communicator.setDefaultRouter(Ice.RouterPrxHelper.uncheckedCast(finder.ice_identity(ident))); } } try { dispatchCallbackAndWait(() => { _callback.createdCommunicator(this); }); Glacier2.RouterPrx routerPrx = Glacier2.RouterPrxHelper.uncheckedCast(_communicator.getDefaultRouter()); Glacier2.SessionPrx session = factory(routerPrx); connected(routerPrx, session); } catch(Exception ex) { try { _communicator.destroy(); } catch(Exception) { } dispatchCallback(() => { _callback.connectFailed(this, ex); }, null); } })).Start(); } #if COMPACT private void dispatchCallback(Ice.VoidAction callback, Ice.Connection conn) #else private void dispatchCallback(System.Action callback, Ice.Connection conn) #endif { if(_initData.dispatcher != null) { _initData.dispatcher(callback, conn); } else { callback(); } } #if COMPACT private void dispatchCallbackAndWait(Ice.VoidAction callback) #else private void dispatchCallbackAndWait(System.Action callback) #endif { if(_initData.dispatcher != null) { EventWaitHandle h = new ManualResetEvent(false); _initData.dispatcher(() => { callback(); h.Set(); }, null); h.WaitOne(); } else { callback(); } } private readonly Ice.InitializationData _initData; private Ice.Communicator _communicator; private Ice.ObjectAdapter _adapter; private Glacier2.RouterPrx _router; private Glacier2.SessionPrx _session; private bool _connected = false; private string _category; private string _finderStr; private bool _useCallbacks; private readonly SessionCallback _callback; private bool _destroy = false; } }