View Javadoc

1   package com.atlassian.plugin.classloader;
2   
3   import com.atlassian.plugin.Plugin;
4   import com.atlassian.plugin.PluginAccessor;
5   import org.apache.commons.logging.Log;
6   import org.apache.commons.logging.LogFactory;
7   
8   import java.net.URL;
9   import java.util.Collection;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.HashMap;
13  import java.util.Set;
14  import java.util.HashSet;
15  
16  /**
17   *
18   */
19  public class PluginsClassLoader extends AbstractClassLoader
20  {
21      private static final Log log = LogFactory.getLog(PluginsClassLoader.class);
22      private final PluginAccessor pluginAccessor;
23  
24      private final Map<String,Plugin> pluginResourceIndex = new HashMap<String,Plugin>();
25      private final Map<String,Plugin> pluginClassIndex = new HashMap<String,Plugin>();
26  
27      private final Set<String> missedPluginResource = new HashSet<String>();
28      private final Set<String> missedPluginClass = new HashSet<String>();
29  
30      public PluginsClassLoader(PluginAccessor pluginAccessor)
31      {
32          this(null,pluginAccessor);
33      }
34  
35      public PluginsClassLoader(ClassLoader parent, PluginAccessor pluginAccessor)
36      {
37          super(parent);
38          if (pluginAccessor == null)
39          {
40              throw new IllegalArgumentException("The plugin accessor should not be null.");
41          }
42          this.pluginAccessor = pluginAccessor;
43      }
44  
45      protected URL findResource(final String name)
46      {
47          final Plugin indexedPlugin;
48          synchronized (this)
49          {
50               indexedPlugin = pluginResourceIndex.get(name);
51          }
52          final URL result;
53          if (isPluginEnabled(indexedPlugin))
54          {
55              result = indexedPlugin.getClassLoader().getResource(name);
56          }
57          else
58          {
59              result = getResourceFromPlugins(name);
60          }
61          if (log.isDebugEnabled())
62          {
63              log.debug("Find resource [ " + name + " ], found [ " + result + " ]");
64          }
65          return result;
66      }
67  
68      protected Class findClass(String className) throws ClassNotFoundException
69      {
70          final Plugin indexedPlugin;
71          synchronized (this)
72          {
73              indexedPlugin = pluginClassIndex.get(className);
74          }
75  
76          final Class result;
77          if (isPluginEnabled(indexedPlugin))
78          {
79              result = indexedPlugin.getClassLoader().loadClass(className);
80          }
81          else
82          {
83              result = loadClassFromPlugins(className);
84          }
85          if (log.isDebugEnabled())
86          {
87              log.debug("Find class [ " + className + " ], found [ " + result + " ]");
88          }
89          if (result != null)
90          {
91              return result;
92          }
93          else
94          {
95              throw new ClassNotFoundException(className);
96          }
97      }
98  
99      private Class loadClassFromPlugins(String className)
100     {
101         final boolean isMissedClassName;
102         synchronized (this)
103         {
104             isMissedClassName = missedPluginClass.contains(className);
105         }
106         if (isMissedClassName)
107         {
108             return null;
109         }
110         final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
111         for (Plugin plugin : plugins)
112         {
113             try
114             {
115                 Class result = plugin.getClassLoader().loadClass(className);
116                 //loadClass should never return null
117                 synchronized (this)
118                 {
119                     pluginClassIndex.put(className, plugin);
120                 }
121                 return result;
122             }
123             catch (ClassNotFoundException e)
124             {
125                 // continue searching the other plugins
126             }
127         }
128         synchronized (this)
129         {
130             missedPluginClass.add(className);
131         }
132         return null;
133     }
134 
135     private URL getResourceFromPlugins(String name)
136     {
137         final boolean isMissedResource;
138         synchronized (this)
139         {
140             isMissedResource = missedPluginResource.contains(name);
141         }
142         if (isMissedResource)
143         {
144             return null;
145         }
146         final Collection<Plugin> plugins = pluginAccessor.getEnabledPlugins();
147         for (Plugin plugin : plugins)
148         {
149             final URL resource = plugin.getClassLoader().getResource(name);
150             if (resource != null)
151             {
152                 synchronized (this)
153                 {
154                     pluginResourceIndex.put(name, plugin);
155                 }
156                 return resource;
157             }
158         }
159         synchronized (this)
160         {
161             missedPluginResource.add(name);
162         }
163         return null;
164     }
165 
166     private boolean isPluginEnabled(Plugin plugin)
167     {
168         return plugin != null && pluginAccessor.isPluginEnabled(plugin.getKey());
169     }
170 
171     public synchronized void notifyUninstallPlugin(Plugin plugin)
172     {
173         flushMissesCaches();
174         for (Iterator it = pluginResourceIndex.entrySet().iterator(); it.hasNext();)
175         {
176             final Map.Entry resourceEntry = (Map.Entry) it.next();
177             final Plugin pluginForResource = (Plugin) resourceEntry.getValue();
178             if (plugin.getKey().equals(pluginForResource.getKey()))
179             {
180                 it.remove();
181             }
182         }
183         for (Iterator it = pluginClassIndex.entrySet().iterator(); it.hasNext();)
184         {
185             final Map.Entry pluginClassEntry = (Map.Entry) it.next();
186             final Plugin pluginForClass = (Plugin) pluginClassEntry.getValue();
187             if (plugin.getKey().equals(pluginForClass.getKey()))
188             {
189                 it.remove();
190             }
191         }
192     }
193 
194     public synchronized void notifyPluginOrModuleEnabled()
195     {
196         flushMissesCaches();
197     }
198 
199     private void flushMissesCaches()
200     {
201         missedPluginClass.clear();
202         missedPluginResource.clear();
203     }
204 }