View Javadoc

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