1 //
2 // Copyright (c) ZeroC, Inc. All rights reserved.
3 //
4 
5 package com.zeroc.Ice;
6 
7 import java.net.URLEncoder;
8 
9 public final class PluginManagerI implements PluginManager
10 {
11     private static String _kindOfObject = "plugin";
12 
13     @Override
14     public synchronized void
initializePlugins()15     initializePlugins()
16     {
17         if(_initialized)
18         {
19             InitializationException ex = new InitializationException();
20             ex.reason = "plug-ins already initialized";
21             throw ex;
22         }
23 
24         //
25         // Invoke initialize() on the plug-ins, in the order they were loaded.
26         //
27         java.util.List<Plugin> initializedPlugins = new java.util.ArrayList<>();
28         try
29         {
30             for(PluginInfo p : _plugins)
31             {
32                 try
33                 {
34                     p.plugin.initialize();
35                 }
36                 catch(PluginInitializationException ex)
37                 {
38                     throw ex;
39                 }
40                 catch(RuntimeException ex)
41                 {
42                     PluginInitializationException e = new PluginInitializationException();
43                     e.reason = "plugin `" + p.name + "' initialization failed";
44                     e.initCause(ex);
45                     throw e;
46                 }
47                 initializedPlugins.add(p.plugin);
48             }
49         }
50         catch(RuntimeException ex)
51         {
52             //
53             // Destroy the plug-ins that have been successfully initialized, in the
54             // reverse order.
55             //
56             java.util.ListIterator<Plugin> i = initializedPlugins.listIterator(initializedPlugins.size());
57             while(i.hasPrevious())
58             {
59                 Plugin p = i.previous();
60                 try
61                 {
62                     p.destroy();
63                 }
64                 catch(RuntimeException e)
65                 {
66                     // Ignore.
67                 }
68             }
69             throw ex;
70         }
71 
72         _initialized = true;
73     }
74 
75     @Override
76     public synchronized String[]
getPlugins()77     getPlugins()
78     {
79         java.util.ArrayList<String> names = new java.util.ArrayList<>();
80         for(PluginInfo p : _plugins)
81         {
82             names.add(p.name);
83         }
84         return names.toArray(new String[0]);
85     }
86 
87     @Override
88     public synchronized Plugin
getPlugin(String name)89     getPlugin(String name)
90     {
91         if(_communicator == null)
92         {
93             throw new CommunicatorDestroyedException();
94         }
95 
96         Plugin p = findPlugin(name);
97         if(p != null)
98         {
99             return p;
100         }
101 
102         NotRegisteredException ex = new NotRegisteredException();
103         ex.id = name;
104         ex.kindOfObject = _kindOfObject;
105         throw ex;
106     }
107 
108     @Override
109     public synchronized void
addPlugin(String name, Plugin plugin)110     addPlugin(String name, Plugin plugin)
111     {
112         if(_communicator == null)
113         {
114             throw new CommunicatorDestroyedException();
115         }
116 
117         if(findPlugin(name) != null)
118         {
119             AlreadyRegisteredException ex = new AlreadyRegisteredException();
120             ex.id = name;
121             ex.kindOfObject = _kindOfObject;
122             throw ex;
123         }
124 
125         PluginInfo info = new PluginInfo();
126         info.name = name;
127         info.plugin = plugin;
128         _plugins.add(info);
129     }
130 
131     @Override
132     public synchronized void
destroy()133     destroy()
134     {
135         if(_communicator != null)
136         {
137             if(_initialized)
138             {
139                 java.util.ListIterator<PluginInfo> i = _plugins.listIterator(_plugins.size());
140                 while(i.hasPrevious())
141                 {
142                     PluginInfo p = i.previous();
143                     try
144                     {
145                         p.plugin.destroy();
146                     }
147                     catch(RuntimeException ex)
148                     {
149                         Util.getProcessLogger().warning("unexpected exception raised by plug-in `" +
150                                                         p.name + "' destruction:\n" + ex.toString());
151                     }
152                 }
153             }
154 
155             _communicator = null;
156         }
157 
158         _plugins.clear();
159 
160         if(_classLoaders != null)
161         {
162             _classLoaders.clear();
163         }
164     }
165 
PluginManagerI(Communicator communicator, com.zeroc.IceInternal.Instance instance)166     public PluginManagerI(Communicator communicator, com.zeroc.IceInternal.Instance instance)
167     {
168         _communicator = communicator;
169         _instance = instance;
170         _initialized = false;
171     }
172 
loadPlugins(String[] cmdArgs)173     public String[] loadPlugins(String[] cmdArgs)
174     {
175         assert(_communicator != null);
176 
177         //
178         // Load and initialize the plug-ins defined in the property set
179         // with the prefix "Ice.Plugin.". These properties should
180         // have the following format:
181         //
182         // Ice.Plugin.name[.<language>]=entry_point [args]
183         //
184         // If the Ice.PluginLoadOrder property is defined, load the
185         // specified plug-ins in the specified order, then load any
186         // remaining plug-ins.
187         //
188         final String prefix = "Ice.Plugin.";
189         Properties properties = _communicator.getProperties();
190         java.util.Map<String, String> plugins = properties.getPropertiesForPrefix(prefix);
191 
192         final String[] loadOrder = properties.getPropertyAsList("Ice.PluginLoadOrder");
193         for(String name : loadOrder)
194         {
195             if(findPlugin(name) != null)
196             {
197                 PluginInitializationException ex = new PluginInitializationException();
198                 ex.reason = "plug-in `" + name + "' already loaded";
199                 throw ex;
200             }
201 
202             String key = "Ice.Plugin." + name + ".java";
203             boolean hasKey = plugins.containsKey(key);
204             if(hasKey)
205             {
206                 plugins.remove("Ice.Plugin." + name);
207             }
208             else
209             {
210                 key = "Ice.Plugin." + name;
211                 hasKey = plugins.containsKey(key);
212             }
213 
214             if(hasKey)
215             {
216                 final String value = plugins.get(key);
217                 cmdArgs = loadPlugin(name, value, cmdArgs);
218                 plugins.remove(key);
219             }
220             else
221             {
222                 PluginInitializationException ex = new PluginInitializationException();
223                 ex.reason = "plug-in `" + name + "' not defined";
224                 throw ex;
225             }
226         }
227 
228         //
229         // Load any remaining plug-ins that weren't specified in PluginLoadOrder.
230         //
231         while(!plugins.isEmpty())
232         {
233             java.util.Iterator<java.util.Map.Entry<String, String> > p = plugins.entrySet().iterator();
234             java.util.Map.Entry<String, String> entry = p.next();
235 
236             String name = entry.getKey().substring(prefix.length());
237 
238             int dotPos = name.lastIndexOf('.');
239             if(dotPos != -1)
240             {
241                 String suffix = name.substring(dotPos + 1);
242                 if(suffix.equals("cpp") || suffix.equals("clr"))
243                 {
244                     //
245                     // Ignored
246                     //
247                     p.remove();
248                 }
249                 else if(suffix.equals("java"))
250                 {
251                     name = name.substring(0, dotPos);
252                     cmdArgs = loadPlugin(name, entry.getValue(), cmdArgs);
253                     p.remove();
254 
255                     //
256                     // Don't want to load this one if it's there!
257                     //
258                     plugins.remove("Ice.Plugin." + name);
259                 }
260                 else
261                 {
262                     //
263                     // Name is just a regular name that happens to contain a dot
264                     //
265                     dotPos = -1;
266                 }
267             }
268 
269             if(dotPos == -1)
270             {
271                 //
272                 // Is there a .java entry?
273                 //
274                 String value = entry.getValue();
275                 p.remove();
276 
277                 String javaValue = plugins.remove("Ice.Plugin." + name + ".java");
278                 if(javaValue != null)
279                 {
280                     value = javaValue;
281                 }
282 
283                 cmdArgs = loadPlugin(name, value, cmdArgs);
284             }
285         }
286 
287         return cmdArgs;
288     }
289 
loadPlugin(String name, String pluginSpec, String[] cmdArgs)290     private String[] loadPlugin(String name, String pluginSpec, String[] cmdArgs)
291     {
292         assert(_communicator != null);
293 
294         //
295         // We support the following formats:
296         //
297         // <class-name> [args]
298         // <jar-file>:<class-name> [args]
299         // <class-dir>:<class-name> [args]
300         // "<path with spaces>":<class-name> [args]
301         // "<path with spaces>:<class-name>" [args]
302         //
303 
304         String[] args;
305         try
306         {
307             args = com.zeroc.IceUtilInternal.Options.split(pluginSpec);
308         }
309         catch(com.zeroc.IceUtilInternal.Options.BadQuote ex)
310         {
311             throw new PluginInitializationException("invalid arguments for plug-in `" + name + "':\n" +
312                                                     ex.getMessage());
313         }
314 
315         assert(args.length > 0);
316 
317         final String entryPoint = args[0];
318 
319         final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
320         boolean absolutePath = false;
321 
322         //
323         // Find first ':' that isn't part of the file path.
324         //
325         int pos = entryPoint.indexOf(':');
326         if(isWindows)
327         {
328             final String driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
329             if(pos == 1 && entryPoint.length() > 2 && driveLetters.indexOf(entryPoint.charAt(0)) != -1 &&
330                (entryPoint.charAt(2) == '\\' || entryPoint.charAt(2) == '/'))
331             {
332                 absolutePath = true;
333                 pos = entryPoint.indexOf(':', pos + 1);
334             }
335             if(!absolutePath)
336             {
337                 absolutePath = entryPoint.startsWith("\\\\");
338             }
339         }
340         else
341         {
342             absolutePath = entryPoint.startsWith("/");
343         }
344 
345         if((pos == -1 && absolutePath) || (pos != -1 && entryPoint.length() <= pos + 1))
346         {
347             //
348             // Class name is missing.
349             //
350             throw new PluginInitializationException("invalid entry point for plug-in `" + name + "':\n" + entryPoint);
351         }
352 
353         //
354         // Extract the JAR file or subdirectory, if any.
355         //
356         String classDir = null; // Path name of JAR file or subdirectory.
357         String className;
358 
359         if(pos == -1)
360         {
361             className = entryPoint;
362         }
363         else
364         {
365             classDir = entryPoint.substring(0, pos).trim();
366             className = entryPoint.substring(pos + 1).trim();
367         }
368 
369         //
370         // Shift the arguments.
371         //
372         String[] tmp = new String[args.length - 1];
373         System.arraycopy(args, 1, tmp, 0, args.length - 1);
374         args = tmp;
375 
376         //
377         // Convert command-line options into properties. First we
378         // convert the options from the plug-in configuration, then
379         // we convert the options from the application command-line.
380         //
381         Properties properties = _communicator.getProperties();
382         args = properties.parseCommandLineOptions(name, args);
383         cmdArgs = properties.parseCommandLineOptions(name, cmdArgs);
384 
385         //
386         // Instantiate the class.
387         //
388         PluginFactory pluginFactory = null;
389         try
390         {
391             Class<?> c = null;
392 
393             //
394             // Use a class loader if the user specified a JAR file or class directory.
395             //
396             if(classDir != null)
397             {
398                 try
399                 {
400                     if(!absolutePath)
401                     {
402                         classDir = new java.io.File(System.getProperty("user.dir") + java.io.File.separator +
403                             classDir).getCanonicalPath();
404                     }
405 
406                     if(!classDir.endsWith(java.io.File.separator) && !classDir.toLowerCase().endsWith(".jar"))
407                     {
408                         classDir += java.io.File.separator;
409                     }
410                     classDir = URLEncoder.encode(classDir, "UTF-8");
411 
412                     //
413                     // Reuse an existing class loader if we have already loaded a plug-in with
414                     // the same value for classDir, otherwise create a new one.
415                     //
416                     ClassLoader cl = null;
417 
418                     if(_classLoaders == null)
419                     {
420                         _classLoaders = new java.util.HashMap<>();
421                     }
422                     else
423                     {
424                         cl = _classLoaders.get(classDir);
425                     }
426 
427                     if(cl == null)
428                     {
429                         final java.net.URL[] url = new java.net.URL[] { new java.net.URL("file", null, classDir) };
430 
431                         //
432                         // Use the custom class loader (if any) as the parent.
433                         //
434                         if(_instance.initializationData().classLoader != null)
435                         {
436                             cl = new java.net.URLClassLoader(url, _instance.initializationData().classLoader);
437                         }
438                         else
439                         {
440                             cl = new java.net.URLClassLoader(url);
441                         }
442 
443                         _classLoaders.put(classDir, cl);
444                     }
445 
446                     c = cl.loadClass(className);
447                 }
448                 catch(java.net.MalformedURLException ex)
449                 {
450                     throw new PluginInitializationException("invalid entry point format `" + pluginSpec + "'", ex);
451                 }
452                 catch(java.io.IOException ex)
453                 {
454                     throw new PluginInitializationException("invalid path in entry point `" + pluginSpec + "'", ex);
455                 }
456                 catch(java.lang.ClassNotFoundException ex)
457                 {
458                     // Ignored
459                 }
460             }
461             else
462             {
463                 c = com.zeroc.IceInternal.Util.getInstance(_communicator).findClass(className);
464             }
465 
466             if(c == null)
467             {
468                 throw new PluginInitializationException("class " + className + " not found");
469             }
470 
471             java.lang.Object obj = c.getDeclaredConstructor().newInstance();
472             try
473             {
474                 pluginFactory = (PluginFactory)obj;
475             }
476             catch(ClassCastException ex)
477             {
478                 throw new PluginInitializationException("class " + className + " does not implement PluginFactory",
479                                                         ex);
480             }
481         }
482         catch(NoSuchMethodException ex)
483         {
484             throw new PluginInitializationException("unable to instantiate class " + className, ex);
485         }
486         catch(java.lang.reflect.InvocationTargetException ex)
487         {
488             throw new PluginInitializationException("unable to instantiate class " + className, ex);
489         }
490         catch(IllegalAccessException ex)
491         {
492             throw new PluginInitializationException("unable to access default constructor in class " + className, ex);
493         }
494         catch(InstantiationException ex)
495         {
496             throw new PluginInitializationException("unable to instantiate class " + className, ex);
497         }
498 
499         //
500         // Invoke the factory.
501         //
502         Plugin plugin = null;
503         try
504         {
505             plugin = pluginFactory.create(_communicator, name, args);
506         }
507         catch(PluginInitializationException ex)
508         {
509             throw ex;
510         }
511         catch(Throwable ex)
512         {
513             throw new PluginInitializationException("exception in factory " + className, ex);
514         }
515 
516         if(plugin == null)
517         {
518             throw new PluginInitializationException("failure in factory " + className);
519         }
520 
521         PluginInfo info = new PluginInfo();
522         info.name = name;
523         info.plugin = plugin;
524         _plugins.add(info);
525 
526         return cmdArgs;
527     }
528 
findPlugin(String name)529     private Plugin findPlugin(String name)
530     {
531         for(PluginInfo p : _plugins)
532         {
533             if(name.equals(p.name))
534             {
535                 return p.plugin;
536             }
537         }
538         return null;
539     }
540 
541     static class PluginInfo
542     {
543         String name;
544         Plugin plugin;
545     }
546 
547     private Communicator _communicator;
548     private com.zeroc.IceInternal.Instance _instance;
549     private java.util.List<PluginInfo> _plugins = new java.util.ArrayList<>();
550     private boolean _initialized;
551     private java.util.Map<String, ClassLoader> _classLoaders;
552 }
553