< Summary

Information
Class: Ice.PluginManagerI.PluginInfo
Assembly: Ice
File(s): /_/csharp/src/Ice/PluginManagerI.cs
Tag: 125_37167941578
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 422
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 1
Fully covered methods: 1
Total methods: 1
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/_/csharp/src/Ice/PluginManagerI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Collections;
 6using System.Diagnostics;
 7
 8namespace Ice;
 9
 10/// <summary>
 11/// Applications implement this interface to provide a plug-in factory
 12/// to the Ice runtime.
 13/// </summary>
 14public interface PluginFactory
 15{
 16    /// <summary>
 17    /// Gets the default and preferred name for plug-ins created by this factory.
 18    /// </summary>
 19    string pluginName { get; }
 20
 21    /// <summary>
 22    /// Called by the Ice runtime to create a new plug-in.
 23    /// </summary>
 24    ///
 25    /// <param name="communicator">The communicator that is in the process of being initialized.</param>
 26    /// <param name="name">The name of the plug-in.</param>
 27    /// <param name="args">The arguments that are specified in the plug-ins configuration.</param>
 28    /// <returns>The plug-in that was created by this method.</returns>
 29    Plugin create(Communicator communicator, string name, string[] args);
 30}
 31
 32internal sealed class PluginManagerI : PluginManager
 33{
 34    private const string _kindOfObject = "plugin";
 35
 36    public void initializePlugins()
 37    {
 38        if (_initialized)
 39        {
 40            throw new InitializationException("Plug-ins already initialized.");
 41        }
 42
 43        //
 44        // Invoke initialize() on the plug-ins, in the order they were loaded.
 45        //
 46        var initializedPlugins = new ArrayList();
 47        try
 48        {
 49            foreach (PluginInfo p in _plugins)
 50            {
 51                try
 52                {
 53                    p.plugin.initialize();
 54                }
 55                catch (PluginInitializationException)
 56                {
 57                    throw;
 58                }
 59                catch (System.Exception ex)
 60                {
 61                    throw new PluginInitializationException($"Plugin '{p.name}' initialization failed.", ex);
 62                }
 63                initializedPlugins.Add(p.plugin);
 64            }
 65        }
 66        catch (System.Exception)
 67        {
 68            //
 69            // Destroy the plug-ins that have been successfully initialized, in the
 70            // reverse order.
 71            //
 72            initializedPlugins.Reverse();
 73            foreach (Plugin p in initializedPlugins)
 74            {
 75                try
 76                {
 77                    p.destroy();
 78                }
 79                catch (System.Exception)
 80                {
 81                    // Ignore.
 82                }
 83            }
 84            throw;
 85        }
 86
 87        _initialized = true;
 88    }
 89
 90    public string[] getPlugins()
 91    {
 92        lock (_mutex)
 93        {
 94            var names = new ArrayList();
 95            foreach (PluginInfo p in _plugins)
 96            {
 97                names.Add(p.name);
 98            }
 99            return (string[])names.ToArray(typeof(string));
 100        }
 101    }
 102
 103    public Plugin getPlugin(string name)
 104    {
 105        lock (_mutex)
 106        {
 107            if (_communicator is null)
 108            {
 109                throw new CommunicatorDestroyedException();
 110            }
 111
 112            Plugin? p = findPlugin(name);
 113            if (p is not null)
 114            {
 115                return p;
 116            }
 117
 118            throw new NotRegisteredException(_kindOfObject, name);
 119        }
 120    }
 121
 122    public void addPlugin(string name, Plugin plugin)
 123    {
 124        lock (_mutex)
 125        {
 126            if (_communicator is null)
 127            {
 128                throw new CommunicatorDestroyedException();
 129            }
 130
 131            if (findPlugin(name) is not null)
 132            {
 133                throw new AlreadyRegisteredException(_kindOfObject, name);
 134            }
 135
 136            _plugins.Add(new PluginInfo(name, plugin));
 137        }
 138    }
 139
 140    public void destroy()
 141    {
 142        lock (_mutex)
 143        {
 144            if (_communicator is not null)
 145            {
 146                if (_initialized)
 147                {
 148                    var plugins = (ArrayList)_plugins.Clone();
 149                    plugins.Reverse();
 150                    foreach (PluginInfo p in plugins)
 151                    {
 152                        try
 153                        {
 154                            p.plugin.destroy();
 155                        }
 156                        catch (System.Exception ex)
 157                        {
 158                            Util.getProcessLogger().warning("unexpected exception raised by plug-in `" +
 159                                                            p.name + "' destruction:\n" + ex.ToString());
 160                        }
 161                    }
 162                }
 163
 164                _communicator = null;
 165            }
 166        }
 167    }
 168
 169    internal PluginManagerI(Communicator communicator)
 170    {
 171        _communicator = communicator;
 172        _plugins = new ArrayList();
 173        _initialized = false;
 174    }
 175
 176    internal void loadPlugins()
 177    {
 178        Debug.Assert(_communicator is not null);
 179        string prefix = "Ice.Plugin.";
 180        Properties properties = _communicator.getProperties();
 181        Dictionary<string, string> plugins = properties.getPropertiesForPrefix(prefix);
 182
 183        // First, create plug-ins using the plug-in factories from initData, in order.
 184        foreach (PluginFactory pluginFactory in _communicator.instance.initializationData().pluginFactories)
 185        {
 186            string name = pluginFactory.pluginName;
 187            string key = $"Ice.Plugin.{name}";
 188            if (plugins.TryGetValue(key, out string? pluginSpec))
 189            {
 190                loadPlugin(pluginFactory, name, pluginSpec);
 191                plugins.Remove(key);
 192            }
 193            else
 194            {
 195                loadPlugin(pluginFactory, name, "");
 196            }
 197        }
 198
 199        //
 200        // Load and initialize the plug-ins defined in the property set
 201        // with the prefix "Ice.Plugin.". These properties should
 202        // have the following format:
 203        //
 204        // Ice.Plugin.<name>=entry_point [args]
 205        //
 206        // The code below is different from the Java/C++ algorithm
 207        // because C# must support full assembly names such as:
 208        //
 209        // Ice.Plugin.Logger=logger, Version=0.0.0.0, Culture=neutral:LoginPluginFactory
 210        //
 211        // If the Ice.PluginLoadOrder property is defined, load the
 212        // specified plug-ins in the specified order, then load any
 213        // remaining plug-ins.
 214        //
 215
 216        string[] loadOrder = properties.getIcePropertyAsList("Ice.PluginLoadOrder");
 217        for (int i = 0; i < loadOrder.Length; ++i)
 218        {
 219            if (loadOrder[i].Length == 0)
 220            {
 221                continue;
 222            }
 223
 224            if (findPlugin(loadOrder[i]) is not null)
 225            {
 226                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' already loaded.");
 227            }
 228
 229            string key = $"Ice.Plugin.{loadOrder[i]}";
 230            plugins.TryGetValue(key, out string? value);
 231            if (value is not null)
 232            {
 233                loadPlugin(null, loadOrder[i], value);
 234                plugins.Remove(key);
 235            }
 236            else
 237            {
 238                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' not defined.");
 239            }
 240        }
 241
 242        //
 243        // Load any remaining plug-ins that weren't specified in PluginLoadOrder.
 244        //
 245        foreach (KeyValuePair<string, string> entry in plugins)
 246        {
 247            loadPlugin(null, entry.Key[prefix.Length..], entry.Value);
 248        }
 249    }
 250
 251    private void loadPlugin(PluginFactory? pluginFactory, string name, string pluginSpec)
 252    {
 253        Debug.Assert(_communicator is not null);
 254
 255        if (findPlugin(name) is not null)
 256        {
 257            throw new AlreadyRegisteredException(_kindOfObject, name);
 258        }
 259
 260        string[] args = [];
 261        string entryPoint = "";
 262        if (pluginSpec.Length > 0)
 263        {
 264            //
 265            // Split the entire property value into arguments. An entry point containing spaces
 266            // must be enclosed in quotes.
 267            //
 268            try
 269            {
 270                args = Ice.UtilInternal.Options.split(pluginSpec);
 271            }
 272            catch (ParseException ex)
 273            {
 274                throw new PluginInitializationException($"Invalid arguments for plug-in '{name}'.", ex);
 275            }
 276
 277            if (args.Length == 0)
 278            {
 279                throw new PluginInitializationException($"No entry point specified for plug-in '{name}'.");
 280            }
 281
 282            entryPoint = args[0];
 283
 284            //
 285            // Shift the arguments.
 286            //
 287            string[] tmp = new string[args.Length - 1];
 288            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 289            args = tmp;
 290
 291            // Convert command-line options into properties.
 292            Properties properties = _communicator.getProperties();
 293            args = properties.parseCommandLineOptions(name, args);
 294        }
 295
 296        string err = "unable to load plug-in `" + entryPoint + "': ";
 297
 298        if (pluginFactory is null)
 299        {
 300            //
 301            // Extract the assembly name and the class name.
 302            //
 303            int sepPos = entryPoint.IndexOf(':', StringComparison.Ordinal);
 304            if (sepPos != -1)
 305            {
 306                const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 307                if (entryPoint.Length > 3 &&
 308                   sepPos == 1 &&
 309                   driveLetters.Contains(entryPoint[0], StringComparison.Ordinal) &&
 310                   (entryPoint[2] == '\\' || entryPoint[2] == '/'))
 311                {
 312                    sepPos = entryPoint.IndexOf(':', 3);
 313                }
 314            }
 315            if (sepPos == -1)
 316            {
 317                throw new PluginInitializationException($"{err}invalid entry point format");
 318            }
 319
 320            string assemblyName = entryPoint[..sepPos];
 321            string className = entryPoint[(sepPos + 1)..];
 322
 323            System.Reflection.Assembly? pluginAssembly;
 324            try
 325            {
 326                //
 327                // First try to load the assembly using Assembly.Load, which will succeed
 328                // if a fully-qualified name is provided or if a partial name has been qualified
 329                // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed
 330                // if a file name is configured or a partial name is configured and DEVPATH is used.
 331                //
 332                // We catch System.Exception as this can fail with System.ArgumentNullException
 333                // or System.IO.IOException depending of the .NET framework and platform.
 334                //
 335                try
 336                {
 337                    pluginAssembly = System.Reflection.Assembly.Load(assemblyName);
 338                }
 339                catch (System.Exception ex)
 340                {
 341                    try
 342                    {
 343                        pluginAssembly = System.Reflection.Assembly.LoadFrom(assemblyName);
 344                    }
 345                    catch (System.IO.IOException)
 346                    {
 347#pragma warning disable CA2200 // Rethrow to preserve stack details
 348                        throw ex;
 349#pragma warning restore CA2200 // Rethrow to preserve stack details
 350                    }
 351                }
 352            }
 353            catch (System.Exception ex)
 354            {
 355                throw new PluginInitializationException($"{err}unable to load assembly '{assemblyName}'.", ex);
 356            }
 357
 358            //
 359            // Instantiate the class.
 360            //
 361            Type? c;
 362            try
 363            {
 364                c = pluginAssembly.GetType(className, true);
 365            }
 366            catch (System.Exception ex)
 367            {
 368                throw new PluginInitializationException($"{err}GetType failed for '{className}'.", ex);
 369            }
 370
 371            try
 372            {
 373                pluginFactory = (PluginFactory)Ice.Internal.AssemblyUtil.createInstance(c) ??
 374                    throw new PluginInitializationException($"{err}can't find constructor for '{className}'.");
 375            }
 376            catch (System.Exception ex)
 377            {
 378                throw new PluginInitializationException($"{err}SystemException", ex);
 379            }
 380        }
 381
 382        Plugin? plugin;
 383        try
 384        {
 385            plugin = pluginFactory.create(_communicator, name, args);
 386        }
 387        catch (PluginInitializationException)
 388        {
 389            throw;
 390        }
 391        catch (System.Exception ex)
 392        {
 393            throw new PluginInitializationException($"{err}System.Exception in factory.create", ex);
 394        }
 395
 396        if (plugin is null)
 397        {
 398            throw new PluginInitializationException($"{err}factory.create returned null plug-in");
 399        }
 400
 401        _plugins.Add(new PluginInfo(name, plugin));
 402    }
 403
 404    private Plugin? findPlugin(string name)
 405    {
 406        foreach (PluginInfo p in _plugins)
 407        {
 408            if (name.Equals(p.name, StringComparison.Ordinal))
 409            {
 410                return p.plugin;
 411            }
 412        }
 413        return null;
 414    }
 415
 1416    internal record class PluginInfo(string name, Plugin plugin);
 417
 418    private Communicator? _communicator;
 419    private readonly ArrayList _plugins;
 420    private bool _initialized;
 421    private readonly object _mutex = new();
 422}

Methods/Properties

.ctor(string, Ice.Plugin)