#ifndef CORELIB___PLUGIN_MANAGER__HPP
#define CORELIB___PLUGIN_MANAGER__HPP

/* $Id: plugin_manager.hpp 606489 2020-04-22 16:40:56Z ivanov $
 * ===========================================================================
 *
 *                            PUBLIC DOMAIN NOTICE
 *               National Center for Biotechnology Information
 *
 *  This software/database is a "United States Government Work" under the
 *  terms of the United States Copyright Act.  It was written as part of
 *  the author's official duties as a United States Government employee and
 *  thus cannot be copyrighted.  This software/database is freely available
 *  to the public for use. The National Library of Medicine and the U.S.
 *  Government have not placed any restriction on its use or reproduction.
 *
 *  Although all reasonable efforts have been taken to ensure the accuracy
 *  and reliability of the software and data, the NLM and the U.S.
 *  Government do not and cannot warrant the performance or results that
 *  may be obtained by using this software or data. The NLM and the U.S.
 *  Government disclaim all warranties, express or implied, including
 *  warranties of performance, merchantability or fitness for any particular
 *  purpose.
 *
 *  Please cite the author in any work or product based on this material.
 *
 * ===========================================================================
 *
 * Author:  Denis Vakatov, Anatoliy Kuznetsov
 *
 * File Description:  Plugin manager (using class factory paradigm)
 *
 */

/// @file plugin_manager.hpp
/// Plugin manager (using class factory paradigm).
///
/// Describe generic interface and provide basic functionality to advertise
/// and export a class factory.
/// The class and class factory implementation code can be linked to
/// either statically (then, the class factory will need to be registered
/// explicitly by the user code) or dynamically (then, the DLL will be
/// searched for using plugin name, and the well-known DLL entry point
/// will be used to register the class factory, automatically).
///
/// - "class factory" -- An entity used to generate objects of the given class.
///                      One class factory can generate more than one version
///                      of the class.
///
/// - "interface"  -- Defines the implementation-independent API and expected
///                   behavior of a class.
///                   Interface's name is provided by its class's factory,
///                   see IClassFactory::GetInterfaceName().
///                   Interfaces are versioned to track the compatibility.
///
/// - "driver"  -- A concrete implementation of the interface and its factory.
///                Each driver has its own name (do not confuse it with the
///                interface name!) and version.
///
/// - "host"    -- An entity (DLL or the EXEcutable itself) that contains
///                one or more drivers (or versions of the same driver),
///                which can implement one or more interfaces.
///
/// - "version" -- MAJOR (backward- and forward-incompatible changes in the
///                       interface and/or its expected behavior);
///                MINOR (backward compatible changes in the driver code);
///                PATCH_LEVEL (100% compatible plugin or driver code changes).
///

#include <corelib/ncbimtx.hpp>
#include <corelib/version_api.hpp>
#include <corelib/ncbidll.hpp>
#include <corelib/ncbi_config.hpp>
#include <corelib/ncbiapp_api.hpp>
#include <corelib/error_codes.hpp>

#include <set>
#include <string>


BEGIN_NCBI_SCOPE

/** @addtogroup PluginMgr
 *
 * @{
 */

/////////////////////////////////////////////////////////////////////////////
///
/// CPluginManagerException --
///
/// Exception generated by CPluginManager.

class NCBI_XNCBI_EXPORT CPluginManagerException : public CCoreException
{
public:
    enum EErrCode {
        eResolveFailure,       ///< Cannot resolve interface driver
        eParameterMissing,     ///< Missing mandatory parameter
        eNullInstance          ///< Factory returned NULL instance
    };

    /// Translate from the error code value to its string representation.
    virtual const char* GetErrCodeString(void) const override;

    // Standard exception boilerplate code.
    NCBI_EXCEPTION_DEFAULT(CPluginManagerException, CCoreException);
};


/// CInterfaceVersion<> --
///
/// Interface version traits
/// Current interface version.
///
/// It is just a boilerplate, to be hard-coded in the concrete interface header
/// @sa NCBI_PLUGIN_VERSION, CVersionInfo

template <class TClass>
class CInterfaceVersion
{
};


/// Macro to auto-setup the current interface name and version.
///
/// This macro must be "called" once per interface, usually in the
/// very header that describes that interface.
///
/// Example:
///    NCBI_INTERFACE_VERSION(IFooBar, "IFooBar", 1, 3, 8);
/// @sa CInterfaceVersion

#define NCBI_DECLARE_INTERFACE_VERSION(iface, iface_name, major, minor, patch_level) \
template<> \
class CInterfaceVersion<iface> \
{ \
public: \
    enum { \
        eMajor      = major, \
        eMinor      = minor, \
        ePatchLevel = patch_level \
    }; \
    static const char* GetName() { return iface_name; } \
}

/// Macro to construct CVersionInfo class using interface name
/// (relies on CInterfaceVersion class)
/// @sa CVersionInfo
#define NCBI_INTERFACE_VERSION(iface) \
CVersionInfo(ncbi::CInterfaceVersion<iface>::eMajor, \
             ncbi::CInterfaceVersion<iface>::eMinor, \
             ncbi::CInterfaceVersion<iface>::ePatchLevel)

// Driver version traits
template<class TInterface>
class CDefaultDriverVersion
{
public:
    enum {
        eMajor      = ncbi::CInterfaceVersion<TInterface>::eMajor,
        eMinor      = ncbi::CInterfaceVersion<TInterface>::eMinor,
        ePatchLevel = ncbi::CInterfaceVersion<TInterface>::ePatchLevel
    };
};

typedef CConfig::TParamTree TPluginManagerParamTree;


struct SDriverInfo
{
    string         name;        ///< Driver name
    CVersionInfo   version;     ///< Driver version

    SDriverInfo(const string&       driver_name,
                const CVersionInfo& driver_version)
        : name(driver_name),
          version(driver_version)
    {}
};

typedef list<SDriverInfo>  TDriverList;

/// IClassFactory<> --
///
/// Class factory for the given interface.
///
/// IClassFactory should be implemented for collection of drivers
/// and exported by hosts

template <class TClass>
class IClassFactory
{
public:
    typedef TClass                        TInterface;
    typedef ncbi::SDriverInfo             SDriverInfo;
    typedef ncbi::TDriverList             TDriverList;
    typedef CDefaultDriverVersion<TClass> TDefaultDriverVersion;

    static const CVersionInfo& GetDefaultDrvVers(void)
    {
        static const CVersionInfo vi(TDefaultDriverVersion::eMajor,
                                     TDefaultDriverVersion::eMinor,
                                     TDefaultDriverVersion::ePatchLevel);
        return vi;
    }

    /// Create driver's instance
    ///
    /// Function creates driver by its name and version.
    /// The requirements is the drivers version should match the interface
    /// up to the patch level.
    ///
    /// @param driver
    ///  Requested driver's name (not the name of the supported interface)
    /// @param version
    ///  Requested interface version (as understood by the caller).
    ///  By default it will be passed the version which is current from
    ///  the calling code's point of view.
    /// @param
    /// @return
    ///  NULL on any error (not found entry point, version mismatch, etc.)
    virtual TClass* CreateInstance
      (const string&  driver  = kEmptyStr,
       CVersionInfo   version = GetDefaultDrvVers(),
       const TPluginManagerParamTree* params = 0) const = 0;

    /// Versions of the interface exported by the factory
    virtual void GetDriverVersions(TDriverList& driver_list) const = 0;

    virtual ~IClassFactory(void) {}

protected:

    /// Utility function to get an element of parameter tree
    /// Throws an exception when mandatory parameter is missing
    /// (or returns the default value)

    string GetParam(const string&                  driver_name,
                    const TPluginManagerParamTree* params,
                    const string&                  param_name,
                    bool                           mandatory,
                    const string&                  default_value) const;

    /// This version always defaults to the empty string so that it
    /// can safely return a reference.  (default_value may be
    /// temporary in some cases.)
    const string& GetParam(const string&                  driver_name,
                           const TPluginManagerParamTree* params,
                           const string&                  param_name,
                           bool                           mandatory) const;
};



class CPluginManager_DllResolver;


struct CPluginManagerBase : public CObject
{
};


/// Template function to create dll resolver for interface.
/// Interfaces which require dll resolvers should define
/// specialization of the template.
template <class TClass>
class CDllResolver_Getter
{
public:
    CPluginManager_DllResolver* operator()(void)
    {
        return 0;
    }
};


/// CPluginManager<> --
///
/// To register (either directly, or via an "entry point") class factories
/// for the given interface.
///
/// Then, to facilitate the process of instantiating the class given
/// the registered pool of drivers, and also taking into account the driver name
/// and/or version as requested by the calling code.
///
/// Template class is protected by mutex and safe for use from different threads

// class TIfVer is a *default* interface version for all CPluginManager's methods.

template <class TClass>
class CPluginManager : public CPluginManagerBase
{
public:
    typedef IClassFactory<TClass>         TClassFactory;
    typedef CDefaultDriverVersion<TClass> TDefaultDriverVersion;

    /// Container for the DLL search paths
    /// @sa ResetDllSearchPath
    typedef vector<string>                TSearchPaths;

    static const CVersionInfo& GetDefaultDrvVers(void)
    {
        static const CVersionInfo vi(TDefaultDriverVersion::eMajor,
                                     TDefaultDriverVersion::eMinor,
                                     TDefaultDriverVersion::ePatchLevel);
        return vi;
    }

    /// Create class instance
    /// @return
    ///  Never returns NULL -- always throw exception on error.
    /// @sa GetFactory()
    TClass* CreateInstance(const string&       driver  = kEmptyStr,
                           const CVersionInfo& version = GetDefaultDrvVers(),
                           const TPluginManagerParamTree* params = 0)
    {
        string driver_name = driver;
        TSubstituteMap::const_iterator subst_it = m_SubstituteMap.find(driver_name);

        if (subst_it != m_SubstituteMap.end()) {
            driver_name = subst_it->second;
        }

        TClassFactory* factory = GetFactory(driver_name, version);
        TClass* drv = factory->CreateInstance(driver_name, version, params);
        if (!drv) {
            string msg = "Cannot create a driver instance (driver: ";
            msg += driver;
            msg += ").";
            NCBI_THROW(CPluginManagerException, eNullInstance, msg);
        }
        return drv;
    }

    /// Create first available driver from the list of drivers.
    /// Several driver names may be separated with ":".
    TClass* CreateInstanceFromList
        (const TPluginManagerParamTree* params,
         const string&                  driver_list,
         const CVersionInfo&            version = GetDefaultDrvVers());

    /// Detect driver from the parameters using the key to get list of drivers.
    TClass* CreateInstanceFromKey
        (const TPluginManagerParamTree* params,
         const string&                  driver_key,
         const CVersionInfo&            version = GetDefaultDrvVers());

    /// Get class factory
    ///
    /// If more than one (of registered) class factory contain eligible
    /// driver candidates, then pick the class factory containing driver of
    /// the latest version.
    /// @param driver
    ///  Name of the driver. If passed empty, then -- any.
    /// @param version
    ///  Requested version. The returned driver can have a different (newer)
    ///  version (provided that the new implementation is backward-compatible
    ///  with the requested version.
    /// @return
    ///  Never return NULL -- always throw exception on error.
    TClassFactory* GetFactory(const string&       driver  = kEmptyStr,
                              const CVersionInfo& version = GetDefaultDrvVers());

    #ifndef NCBI_SWIG
    /// Information about a driver, with maybe a pointer to an instantiated
    /// class factory that contains the driver.
    /// @sa FNCBI_EntryPoint
    struct SDriverInfo : public ncbi::SDriverInfo {
        // It's the plugin manager's (and not SDriverInfo) responsibility to
        // keep and then destroy class factories.
        TClassFactory* factory;     ///< Class factory (can be NULL)

        SDriverInfo(const string&       driver_name,
                      const CVersionInfo& driver_version)
            : ncbi::SDriverInfo(driver_name, driver_version),
              factory(0)
        {}
    };
    #endif

    /// List of driver information.
    ///
    /// It is used to communicate using the entry points mechanism.
    /// @sa FNCBI_EntryPoint
    typedef list<SDriverInfo> TDriverInfoList;

    /// Try to register factory in the manager.
    ///
    /// The registered factory will be owned by the manager.
    /// @return
    ///  true if a factory was registered.
    /// @sa UnregisterFactory()
    bool RegisterFactory(TClassFactory& factory);

    /// Check if a given factory will extend capabilities of the Plugin
    /// Manager (add either new drivers or new driver versions to already
    /// available).
    ///
    /// @sa RegisterFactory()
    bool WillExtendCapabilities(TClassFactory& factory) const;

    /// Unregister and release (un-own)
    /// @sa RegisterFactory()
    bool UnregisterFactory(TClassFactory& factory);

    /// Actions performed by the entry point
    /// @sa FNCBI_EntryPoint
    enum EEntryPointRequest {
        /// Add info about all drivers exported through the entry point
        /// to the end of list.
        ///
        /// "SFactoryInfo::factory" in the added info should be assigned NULL.
        eGetFactoryInfo,

        /// Scan the driver info list passed to the entry point for the
        /// [name,version] pairs exported by the given entry point.
        ///
        /// For each pair found, if its "SDriverInfo::factory" is NULL,
        /// instantiate its class factory and assign it to the
        /// "SDriverInfo::factory".
        eInstantiateFactory
    };

    /// Entry point to get drivers' info, and (if requested) their class
    /// factories.
    ///
    /// This function is usually (but not necessarily) called by
    /// RegisterWithEntryPoint().
    ///
    /// Usually, it's called twice -- the first time to get the info
    /// about the drivers exported by the entry point, and then
    /// to instantiate selected factories.
    ///
    /// Caller is responsible for the proper destruction (deallocation)
    /// of the instantiated factories.
    typedef void (*FNCBI_EntryPoint)(TDriverInfoList&   info_list,
                                     EEntryPointRequest method);

    /// Register all factories exported by the plugin entry point.
    /// @return true if at least one factory was registered.
    /// @sa RegisterFactory()
    template <typename TEntryPoint>
    bool RegisterWithEntryPoint(TEntryPoint plugin_entry_point);

    /// Register all compatible factories for the driver with the particular
    /// version exported by the plugin entry point.
    /// @return true if at least one factory was registered.
    /// @sa RegisterFactory()
    template <typename TEntryPoint>
    bool RegisterWithEntryPoint(TEntryPoint         plugin_entry_point,
                                const string&       driver_name,
                                const CVersionInfo& driver_version =
                                    CVersionInfo::kLatest);

    /// Attach DLL resolver to plugin manager
    ///
    /// Plugin manager uses all attached resolvers to search for DLLs
    /// exporting drivers of this interface.
    ///
    /// @param resolver
    ///   DLL resolver. Plugin manager takes ownership of the resolver.
    ///
    /// @sa DetachResolver
    void AddResolver(CPluginManager_DllResolver* resolver);

    /// Remove resolver from the list of active resolvers.
    ///
    /// Method is used when we need to freeze some of the resolution variants
    /// Resolver is not deleted, and can be reattached again by AddResolver
    ///
    /// @param resolver
    ///   DLL resolver. Ownership is returned to the caller and resolver
    ///   should be deleted by the caller
    ///
    /// @return Pointer on the detached resolver (same as resolver parameter)
    /// or NULL if resolver not found
    CPluginManager_DllResolver*
    DetachResolver(CPluginManager_DllResolver* resolver);

    /// Add path for the DLL lookup (for all resolvers)
    /// @param path
    ///  Additional path for the DLL lookup
    /// @sa ResetDllSearchPath
    void AddDllSearchPath(const string& path);

    /// Delete all user-installed paths for the DLL lookup (for all resolvers)
    /// @param previous_paths
    ///  If non-NULL, store the previously set search paths in this container
    /// @sa AddDllSearchPath
    void ResetDllSearchPath(TSearchPaths* previous_paths = NULL);

    /// Specify which standard locations should be used for the DLL lookup
    /// (for all resolvers). If standard locations are not set explicitly
    /// using this method CDllResolver::fDefaultDllPath will be used by default.
    /// @sa CDllResolver
    CDllResolver::TExtraDllPath
    SetDllStdSearchPath(CDllResolver::TExtraDllPath standard_paths);

    /// Get standard locations which should be used for the DLL lookup.
    /// @sa SetDllStdSearchPath
    CDllResolver::TExtraDllPath
    GetDllStdSearchPath(void) const;

    /// Scan DLLs for specified driver using attached resolvers
    // Former "Resolve"
    void ResolveFile(const string&       driver  = kEmptyStr,
                     const CVersionInfo& version = GetDefaultDrvVers());

    /// Disable/enable DLL resolution (search for class factories in DLLs)
    void FreezeResolution(bool value = true) { m_BlockResolution = value; }


    /// Disable/enable DLL resolution (search for class factories in DLLs)
    /// for the specified driver
    void FreezeResolution(const string& driver, bool value = true);

    // Constructors
    CPluginManager(void);
    virtual ~CPluginManager(void);

protected:
    TClassFactory* FindClassFactory(const string&  driver,
                                    const CVersionInfo& version) const;

    /// Protective mutex to synchronize the access to the plugin manager
    /// from different threads
    CMutex m_Mutex;

    typedef set<FNCBI_EntryPoint>                TEntryPoints;
    typedef set<TClassFactory*>                  TFactories;
    typedef vector<CDllResolver::SResolvedEntry> TResolvedEntries;
    typedef vector<CPluginManager_DllResolver*>  TDllResolvers;
    typedef set<string>                          TStringSet;
    typedef map<string, string>                  TSubstituteMap;

private:
    /// List of factories presently registered with (and owned by)
    /// the plugin manager.
    TFactories                           m_Factories;
    /// List of entry points registered in this plugin manager
    TEntryPoints                         m_EntryPoints;
    /// DLL resolvers
    TDllResolvers                        m_Resolvers;
    /// Paths used for DLL search
    TSearchPaths                         m_DllSearchPaths;
    /// DLL entries resolved and registered with dll resolver(s)
    TResolvedEntries                     m_RegisteredEntries;
    /// Flag, prohibits DLL resolution
    bool                                 m_BlockResolution;
    /// Set of drivers prohibited from DLL resolution
    TStringSet                           m_FreezeResolutionDrivers;
    /// Standard locations that should be used for the DLL lookup.
    CDllResolver::TExtraDllPath          m_StdDllPath;
    /// Driver name substitution map
    TSubstituteMap                       m_SubstituteMap;
};




/// Service class for DLLs resolution.
///
/// Class is used by CPluginManager to scan directories for DLLs,
/// load and resolve entry points.
///
class NCBI_XNCBI_EXPORT CPluginManager_DllResolver
{
public:
    /// Container for the DLL search paths
    typedef vector<string>  TSearchPaths;

    //
    CPluginManager_DllResolver(void);

    /// Construction
    ///
    /// @param interface_name
    ///   Target interface name
    /// @param plugin_name
    ///   Plugin family name (dbapi, xloader, etc)
    /// @param driver_name
    ///   Name of the driver (dblib, id1, etc)
    /// @param version
    ///   Interface version
    /// @param unload_dll
    ///   Whether to unload drivers DLL in the destructor
    CPluginManager_DllResolver(const string&       interface_name,
                               const string&       driver_name = kEmptyStr,
                               const CVersionInfo& version     = CVersionInfo::kAny,
                               CDll::EAutoUnload   unload_dll  = CDll::eNoAutoUnload);

    //
    virtual ~CPluginManager_DllResolver(void);

    /// Search for plugin DLLs, resolve entry points.
    ///
    /// @param paths
    ///   List of directories to scan for DLLs
    /// @param driver_name
    ///   Name of the driver (dblib, id1, etc)
    /// @param version
    ///   Interface version
    /// @return
    ///   Reference on DLL resolver holding all entry points
    // Former "Resolve"
    CDllResolver& ResolveFile(const TSearchPaths&   paths,
                              const string&         driver_name = kEmptyStr,
                              const CVersionInfo&   version     = CVersionInfo::kAny,
                              CDllResolver::TExtraDllPath std_path = CDllResolver::fDefaultDllPath);

    /// Search for plugin DLLs, resolve entry points.
    ///
    /// @param paths
    ///   Path to scan for DLLs
    /// @return
    ///   Reference on DLL resolver holding all entry points
    CDllResolver& Resolve(const string& path);

    /// Return dll file name. Name does not include path.
    ///
    /// Example:
    ///    "ncbi_plugin_dbapi_ftds_3_1_7".
    ///    "ncbi_pulgin_dbapi_ftds.so.3.1.7"
    /// In this case, the DLL will be searched for in the standard
    /// DLL search paths, with automatic addition of any platform-specific
    /// prefixes and suffixes.
    ///
    /// @param driver_name
    ///    Driver name ("id1", "lds", etc)
    /// @param version
    ///    Requested version of the driver
    virtual
    string GetDllName(const string&       interface_name,
                      const string&       driver_name = kEmptyStr,
                      const CVersionInfo& version     = CVersionInfo::kAny) const;

    enum EVersionLocation { eBeforeSuffix, eAfterSuffix };

    /// Return DLL name mask
    ///
    /// DLL name mask is used for DLL file search.
    ///
    /// Example:
    ///    "ncbi_plugin_objmgr_*.dll"
    ///    "ncbi_plugin_dbapi_ftds.so.*"
    virtual
    string GetDllNameMask(const string&       interface_name,
                          const string&       driver_name = kEmptyStr,
                          const CVersionInfo& version     = CVersionInfo::kAny,
                          EVersionLocation    ver_lct     = eBeforeSuffix)  const;

    /// Return DLL entry point name:
    ///
    /// Default name pattern is:
    ///  - "NCBI_EntryPoint_interface_driver"
    /// Alternative variants:
    ///  - "NCBI_EntryPoint"
    ///  - "NCBI_EntryPoint_interface"
    ///  - "NCBI_EntryPoint_driver"
    ///
    /// @sa GetEntryPointPrefix
    virtual
    string GetEntryPointName(const string& interface_name = kEmptyStr,
                             const string& driver_name    = kEmptyStr) const;


    /// Return DLL entry point prefix ("NCBI_EntryPoint")
    virtual string GetEntryPointPrefix() const;

    /// Return DLL file name prefix ("ncbi_plugin")
    virtual string GetDllNamePrefix() const;

    /// Set DLL file name prefix
    virtual void SetDllNamePrefix(const string& prefix);

    /// Return name of the driver
    const string& GetDriverName() const { return m_DriverName; }

    /// Return whether to allow loading plugins from DLLs in general
    static bool IsEnabledGlobally();

    /// Return whether to allow loading plugins from DLLs in general by default
    static bool IsEnabledGloballyByDefault();

    /// Enable (or disable, if called with enable = false) loading
    /// plugins from DLLs in general
    static void EnableGlobally(bool enable = true);

    /// Disable loading plugins from DLLs in general
    static void DisableGlobally() { EnableGlobally(false); }

protected:
    CDllResolver* GetCreateDllResolver();
    CDllResolver* CreateDllResolver() const;

protected:
    string              m_DllNamePrefix;
    string              m_EntryPointPrefix;
    string              m_InterfaceName;
    string              m_DriverName;
    CVersionInfo        m_Version;
    CDllResolver*       m_DllResolver;
    CDll::EAutoUnload   m_AutoUnloadDll;
};

/* @} */


/////////////////////////////////////////////////////////////////////////////
//  IMPLEMENTATION of INLINE functions
/////////////////////////////////////////////////////////////////////////////

inline
bool operator==(const SDriverInfo& i1, const SDriverInfo& i2)
{
    return i1.name == i2.name  &&  i1.version == i2.version;
}

inline
bool operator<(const SDriverInfo& i1, const SDriverInfo& i2)
{
    return (i1.name < i2.name  || (i1.name == i2.name && (i1.version < i2.version)));
}


template <class TClass>
TClass* CPluginManager<TClass>::CreateInstanceFromList(
    const TPluginManagerParamTree* params,
    const string&                  driver_list,
    const CVersionInfo&            version)
{
    TClass* drv = 0;

    _TRACE("Creating an instance of a driver having version " <<
           version << " from a list " << driver_list);

    list<string> drivers;
    NStr::Split(driver_list, ":", drivers, NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
    ITERATE ( list<string>, it, drivers ) {
        string drv_name = *it;
        const TPluginManagerParamTree* driver_params = params ?
            params->FindNode(drv_name) : 0;
        try {
            drv = CreateInstance(drv_name, version, driver_params);
        }
        catch ( exception& e ) {
            ERR_POST_XX(Corelib_PluginMgr, 1,
                        drv_name << " is not available ::" << e.what());
        }
        if ( drv ) {
            break;
        }
    }
    return drv;
}


template <class TClass>
TClass* CPluginManager<TClass>::CreateInstanceFromKey(
    const TPluginManagerParamTree* params,
    const string&                  driver_key,
    const CVersionInfo&            version)
{
    _TRACE("Creating an instance of a driver having version " <<
           version << " from a key " << driver_key);
    TClass* drv = 0;
    if ( !params ) {
        return drv;
    }
    const TPluginManagerParamTree* driver_node = params->FindNode(driver_key);
    if ( !driver_node ) {
        return drv;
    }
    string driver_name = driver_node->GetValue().value;
    // Driver name may contain a list of drivers
    drv = CreateInstanceFromList(params, driver_name, version);
    return drv;
}


template <class TClass>
typename CPluginManager<TClass>::TClassFactory*
CPluginManager<TClass>::GetFactory(const string&       driver,
                                   const CVersionInfo& version)
{
    CMutexGuard guard(m_Mutex);

    TClassFactory* cf = 0;

    // Search among already registered factories
    _TRACE("Looking for an already loaded factory for driver " <<
           driver << " having version " << version);

    cf = FindClassFactory(driver, version);
    if (cf) {
        return cf;
    }

    if (!m_BlockResolution) {

        typename TStringSet::const_iterator it =
                 m_FreezeResolutionDrivers.find(driver);

        if (it == m_FreezeResolutionDrivers.end()) {
            // Trying to resolve the driver's factory.
            // 1) Locate and load all appropriate DLLs.
            // 2) Register all compatible versions of factories.
            //    Skip not compatible versions.
            _TRACE("Trying to find appropriate files for driver " <<
                   driver << " having version " << version);
            ResolveFile(driver, version);

            // Re-scanning factories...
            // Trying to find an appropriate factory.
            // Make a best match.
            _TRACE("Trying to find an appropriate factory for driver " <<
                   driver << " having version " << version);
            cf = FindClassFactory(driver, version);
            if (cf) {
                return cf;
            }
        }
    }

    string msg = "Cannot resolve class factory (unknown driver: ";
    msg += driver;
    msg += ").";
    NCBI_THROW(CPluginManagerException, eResolveFailure, msg);
}


template <class TClass>
typename CPluginManager<TClass>::TClassFactory*
CPluginManager<TClass>::FindClassFactory(const string&  driver,
                                         const CVersionInfo& version) const
{
    TClassFactory* best_factory = 0;
    int best_major = -1;
    int best_minor = -1;
    int best_patch_level = -1;

    ITERATE(typename TFactories, it, m_Factories) {
        TClassFactory* cf = *it;

        typename TClassFactory::TDriverList drv_list;

        if (!cf)
            continue;

        cf->GetDriverVersions(drv_list);

        ITERATE(typename TClassFactory::TDriverList, it2, drv_list) {
             const typename TClassFactory::SDriverInfo& drv_info = *it2;
             if (!driver.empty()) {
                if (driver != drv_info.name) {
                    continue;
                }
             }
             const CVersionInfo& vinfo = drv_info.version;
             if (IsBetterVersion(version, vinfo,
                                 best_major, best_minor, best_patch_level))
             {
                best_factory = cf;
             }
        }
    }

    return best_factory;
}


template <class TClass>
bool CPluginManager<TClass>::RegisterFactory(TClassFactory& factory)
{
    CMutexGuard guard(m_Mutex);

    // Because there is no association between the "factory", a driver, and
    // a driver version within m_Factories right now we would like to check
    // manually if same capabilities are already present in m_Factories.
    if ( WillExtendCapabilities(factory) ) {
        m_Factories.insert(&factory);
        return true;
    }
    return false;
}

template <class TClass>
bool CPluginManager<TClass>::WillExtendCapabilities
(TClassFactory& factory) const
{
    typename TClassFactory::TDriverList new_drv_list;

    factory.GetDriverVersions(new_drv_list);

    if ( m_Factories.empty() && !new_drv_list.empty() ) {
        return true;
    }

    // Build a full list of all drivers from all factories.
    TDriverList full_drv_list;
    ITERATE(typename TFactories, it, m_Factories) {
        TClassFactory* const cf = *it;

        if (!cf)
            continue;

        typename TClassFactory::TDriverList drv_list;
        cf->GetDriverVersions(drv_list);
        drv_list.sort();
        full_drv_list.merge(drv_list);
        // Remove duplicates.
        full_drv_list.unique();
    }

    ITERATE(typename TClassFactory::TDriverList, it2, full_drv_list) {
        const typename TClassFactory::SDriverInfo& drv_info = *it2;
        bool has_new_driver = true; // optimistic

        ITERATE(typename TClassFactory::TDriverList, new_it2, new_drv_list) {
            const typename TClassFactory::SDriverInfo& new_drv_info = *new_it2;

            if (new_drv_info.name == drv_info.name
                &&  (new_drv_info.version.Match(drv_info.version) ==
                     CVersionInfo::eFullyCompatible)) {
                _TRACE("Driver " << new_drv_info.name << " having version " <<
                       new_drv_info.version << " is already registered and " <<
                       "won't extend Plugin Manager's capabilities");
                has_new_driver = false;
            }
        }

        if (has_new_driver) {
            // NB: This placement allows partial overlap in the highly
            // unlikely case that new_drv_list identifies multiple
            // drivers (already atypical) and more specifically a mix
            // of old and new drivers.  A stricter policy would be to
            // pull both has_new_driver's definition and this followup
            // check up one level.
            return true;
        }
    }

    ERR_POST_XX(Corelib_PluginMgr, 2,
                Warning << "A duplicate driver factory was found. "
                           "It will be ignored because it won't extend "
                           "Plugin Manager's capabilities." );

    return false;
}

template <class TClass>
bool CPluginManager<TClass>::UnregisterFactory(TClassFactory& factory)
{
    CMutexGuard guard(m_Mutex);

    typename TFactories::iterator it = m_Factories.find(&factory);
    if (it != m_Factories.end()) {
        _TRACE("Unregistering factory");
        delete *it;
        m_Factories.erase(it);
    }
    return true; //?
}


template <class TClass>
template <typename TEntryPoint>
bool CPluginManager<TClass>::RegisterWithEntryPoint
(TEntryPoint plugin_entry_point)
{
    CMutexGuard guard(m_Mutex);
    if ( !m_EntryPoints.insert(
             reinterpret_cast<FNCBI_EntryPoint>(plugin_entry_point))
         .second ) {
        // entry point is already registered
        return false;
    }

    TDriverInfoList drv_list;

    // Get info about all provided factories.
    plugin_entry_point(drv_list, eGetFactoryInfo);

    if ( !drv_list.empty() ) {
        // Retrieve all factories for all drivers from this entry point.
        plugin_entry_point(drv_list, eInstantiateFactory);

        ITERATE(typename TDriverInfoList, it, drv_list) {
            if (it->factory) {
                _TRACE("Registering factory for driver " << it->name <<
                       " having version " << it->version);
                RegisterFactory(*(it->factory));
            }
        }
        return true;
    }
    return false;
}

template <class TClass>
class CInvalidDrvVer
    : public unary_function<typename CPluginManager<TClass>::SDriverInfo, bool>
{
public:
    typedef typename CPluginManager<TClass>::SDriverInfo TValue;

    CInvalidDrvVer(const string& driver_name, const CVersionInfo& vi)
        : m_DriverName(driver_name), m_VersionInfo(vi)
    {}

    bool operator() (const TValue& val)
    {
        return m_DriverName != val.name ||
               val.version.Match(m_VersionInfo) == CVersionInfo::eNonCompatible;
    }

private:
    const string       m_DriverName;
    const CVersionInfo m_VersionInfo;
};


template <class TClass>
template <typename TEntryPoint>
bool CPluginManager<TClass>::RegisterWithEntryPoint(TEntryPoint         plugin_entry_point,
                                                    const string&       driver_name,
                                                    const CVersionInfo& driver_version)
{
    CMutexGuard guard(m_Mutex);
    if ( !m_EntryPoints.insert(
             reinterpret_cast<FNCBI_EntryPoint>(plugin_entry_point))
         .second ) {
        // entry point is already registered
        return false;
    }

    TDriverInfoList drv_list;

    // Get info about all provided factories.
    plugin_entry_point(drv_list, eGetFactoryInfo);

    if ( !drv_list.empty() ) {
        // It is not possible to get a perfect match here.
        // We can only cut absolutely wrong versions of.
        // A perfect match will be found after we load all factories
        // from all DLLs.

        CInvalidDrvVer<TClass> is_invalid(driver_name, driver_version);
        // drv_list.remove_if(is_invalid);
        typedef typename TDriverInfoList::iterator TDrilIt;
        for (TDrilIt it = drv_list.begin();  it != drv_list.end(); ) {
            if (is_invalid(*it)) {
                TDrilIt cur = it;
                ++it;
                drv_list.erase(cur);
            } else {
                ++it;
            }
        }

        // Instantiate selected factories.
        plugin_entry_point(drv_list, eInstantiateFactory);

        bool was_registered = false;
        ITERATE(typename TDriverInfoList, it, drv_list) {
            if (it->factory) {
                _TRACE("Registering factory for driver " << it->name <<
                       " having version " << it->version);
                was_registered |= RegisterFactory(*(it->factory));
            }
        }
        return was_registered;
    }
    return false;
}


template <class TClass>
void CPluginManager<TClass>::AddResolver(CPluginManager_DllResolver* resolver)
{
    _ASSERT(resolver);
    m_Resolvers.push_back(resolver);
}


template <class TClass>
CPluginManager_DllResolver*
CPluginManager<TClass>::DetachResolver(CPluginManager_DllResolver* resolver)
{
    NON_CONST_ITERATE(TDllResolvers, it, m_Resolvers) {
        if (resolver == *it) {
            m_Resolvers.erase(it);
            return resolver;
        }
    }
    return 0;
}


template <class TClass>
void CPluginManager<TClass>::AddDllSearchPath(const string& path)
{
    m_DllSearchPaths.push_back(path);
}


template <class TClass>
void CPluginManager<TClass>::ResetDllSearchPath(TSearchPaths* previous_paths)
{
    if (previous_paths) {
        previous_paths->clear();
        previous_paths->swap(m_DllSearchPaths);
    } else {
        m_DllSearchPaths.clear();
    }
}


template <class TClass>
CDllResolver::TExtraDllPath
CPluginManager<TClass>::SetDllStdSearchPath(CDllResolver::TExtraDllPath standard_paths)
{
    CDllResolver::TExtraDllPath tmp_path = m_StdDllPath;

    m_StdDllPath = standard_paths;
    return tmp_path;
}


template <class TClass>
CDllResolver::TExtraDllPath
CPluginManager<TClass>::GetDllStdSearchPath(void) const
{
    return m_StdDllPath;
}


template <class TClass>
void CPluginManager<TClass>::FreezeResolution(const string& driver, bool value)
{
    if (value) {
        m_FreezeResolutionDrivers.insert(driver);
    } else {
        m_FreezeResolutionDrivers.erase(driver);
    }
}


template <class TClass>
void CPluginManager<TClass>::ResolveFile(const string&       driver,
                                         const CVersionInfo& version)
{
    vector<CDllResolver*> resolvers;

    // Run all resolvers to search for driver
    ITERATE(vector<CPluginManager_DllResolver*>, it, m_Resolvers) {
        // Try to get an exact match.
        CDllResolver* dll_resolver =
            &(*it)->ResolveFile(m_DllSearchPaths, driver, version, m_StdDllPath);

        if ( !version.IsAny() && !version.IsLatest() &&
            dll_resolver->GetResolvedEntries().empty() ) {

            // Try to get at least something (ignore version).
            dll_resolver =
                &(*it)->ResolveFile(m_DllSearchPaths, driver, CVersionInfo::kAny, m_StdDllPath);

            if ( dll_resolver->GetResolvedEntries().empty() ) {
                dll_resolver = 0;
            }
        }
        if ( dll_resolver ) {
            resolvers.push_back(dll_resolver);
        }
    }

    // Now choose the DLL entry point to register the class factory
    NON_CONST_ITERATE(vector<CDllResolver*>, it, resolvers) {
        CDllResolver::TEntries& entry_points = (*it)->GetResolvedEntries();

        ITERATE(CDllResolver::TEntries, ite, entry_points) {
            const CDllResolver::SResolvedEntry& entry = *ite;

            if (entry.entry_points.empty()) {
                continue;
            }

            // We register factories from one entry point only because others
            // are just aliases (point to the same set of factories).
            const CDllResolver::SNamedEntryPoint& epoint = entry.entry_points.front();

            if (epoint.entry_point.func) {
                FNCBI_EntryPoint ep =  (FNCBI_EntryPoint)(void*)epoint.entry_point.func;

                if ( RegisterWithEntryPoint(ep, driver, version) ) {
                    m_RegisteredEntries.push_back(entry);
                } else {
                    ERR_POST_XX(Corelib_PluginMgr, 3,
                        Info << "Couldn't register an entry point "
                        "within a DLL '" << entry.dll->GetName() << "' "
                        "because either an entry point with the same name was already "
                        "registered or it does not provide an appropriate factory." );
                }
            }
        }
        // NOTE !!!:
        // Next call is not necessary and lead to memory leak, but removing it 
        // will break current CPluginManager usage and lead to coredamp in DLL mode on exit.
        entry_points.clear();
    }
}


template <class TClass>
CPluginManager<TClass>::CPluginManager(void)
    : m_BlockResolution( !CPluginManager_DllResolver::IsEnabledGlobally() ),
      m_StdDllPath(CDllResolver::fDefaultDllPath)
{
    const IRegistry* registry(NULL);
    static const char* section_name("PLUGIN_MANAGER_SUBST");

    // Retrieve a driver name substitution map.
    // Get current registry ...
    CNcbiApplicationGuard app = CNcbiApplication::InstanceGuard();
    if (app) {
        registry = &app->GetConfig();
    }

    if (registry) {
        list<string> entries;
        registry->EnumerateEntries(section_name, &entries);
        ITERATE(list<string>, cit, entries) {
            string driver_name = *cit;
            string substitute_name = registry->GetString(section_name, driver_name, driver_name);
            m_SubstituteMap[driver_name] = substitute_name;
        }
    }

    CDllResolver_Getter<TClass> getter;
    CPluginManager_DllResolver* resolver = getter();
    if ( resolver ) {
        AddResolver(resolver);
    }
}


template <class TClass>
CPluginManager<TClass>::~CPluginManager(void)
{
    ITERATE ( typename TFactories, it, m_Factories ) {
        delete *it;
    }
    ITERATE ( typename TDllResolvers, it, m_Resolvers ) {
        delete *it;
    }
}


template <class TClass>
string
IClassFactory<TClass>::GetParam(
                        const string&                  driver_name,
                        const TPluginManagerParamTree* params,
                        const string&                  param_name,
                        bool                           mandatory,
                        const string&                  default_value) const
{
    CConfig conf(params);
    return conf.GetString(driver_name,
                          param_name,
                          mandatory ?
                            CConfig::eErr_Throw : CConfig::eErr_NoThrow,
                          default_value);
}


template <class TClass>
const string&
IClassFactory<TClass>::GetParam(
                        const string&                  driver_name,
                        const TPluginManagerParamTree* params,
                        const string&                  param_name,
                        bool                           mandatory) const
{
    CConfig conf(params);
    return conf.GetString(driver_name,
                          param_name,
                          mandatory ?
                            CConfig::eErr_Throw : CConfig::eErr_NoThrow);
}


END_NCBI_SCOPE

#endif  /* CORELIB___PLUGIN_MANAGER__HPP */
