< Summary

Information
Class: Ice.PluginManagerI.PluginInfo
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/PluginManagerI.cs
Tag: 71_18251537082
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 420
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 6
Total methods: 6
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_name()100%11100%
set_name(...)100%210%
get_plugin()100%11100%
set_plugin(...)100%210%
.ctor(...)100%210%

File(s)

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