1 package com.atlassian.plugin.manager;
2
3 import com.atlassian.plugin.Plugin;
4 import com.atlassian.plugin.PluginAccessor;
5 import com.atlassian.plugin.PluginController;
6 import com.atlassian.plugin.PluginState;
7 import com.atlassian.plugin.exception.PluginExceptionInterception;
8 import com.atlassian.plugin.util.PluginUtils;
9 import com.atlassian.plugin.util.WaitUntil;
10 import com.google.common.collect.ImmutableList;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.Set;
19 import java.util.concurrent.CopyOnWriteArraySet;
20 import java.util.concurrent.TimeUnit;
21
22
23
24
25
26
27
28
29 class PluginEnabler
30 {
31 private static final Logger log = LoggerFactory.getLogger(PluginEnabler.class);
32 private static final long LAST_PLUGIN_TIMEOUT = 30 * 1000;
33 private static final long LAST_PLUGIN_WARN_TIMEOUT = 5 * 1000;
34
35 private final PluginAccessor pluginAccessor;
36 private final PluginController pluginController;
37 private final PluginExceptionInterception pluginExceptionInterception;
38 private final Set<Plugin> pluginsBeingEnabled = new CopyOnWriteArraySet<Plugin>();
39
40 public PluginEnabler(PluginAccessor pluginAccessor, PluginController pluginController, final PluginExceptionInterception pluginExceptionInterception)
41 {
42 this.pluginAccessor = pluginAccessor;
43 this.pluginController = pluginController;
44 this.pluginExceptionInterception = pluginExceptionInterception;
45 }
46
47
48
49
50
51
52
53
54 Collection<Plugin> enableAllRecursively(Collection<Plugin> plugins)
55 {
56 Collection<Plugin> pluginsToEnable = new ArrayList<Plugin>();
57 Set<String> dependentKeys = new HashSet<String>();
58
59 for (Plugin plugin : plugins)
60 {
61 scanDependencies(plugin, dependentKeys);
62 }
63
64 for (String key : dependentKeys)
65 {
66 pluginsToEnable.add(pluginAccessor.getPlugin(key));
67 }
68 enable(pluginsToEnable);
69
70 ImmutableList.Builder<Plugin> enabledPlugins = new ImmutableList.Builder<Plugin>();
71 for (Plugin plugin : pluginsToEnable)
72 {
73 if (plugin.getPluginState().equals(PluginState.ENABLED))
74 {
75 enabledPlugins.add(plugin);
76 }
77 }
78 return enabledPlugins.build();
79 }
80
81
82
83
84
85 boolean isPluginBeingEnabled(Plugin plugin)
86 {
87 return pluginsBeingEnabled.contains(plugin);
88 }
89
90
91
92
93
94
95
96 void enable(Collection<Plugin> plugins)
97 {
98 pluginsBeingEnabled.addAll(plugins);
99 try
100 {
101 actualEnable(plugins);
102 }
103 finally
104 {
105 pluginsBeingEnabled.removeAll(plugins);
106 }
107 }
108
109 private void actualEnable(Collection<Plugin> plugins)
110 {
111 final Set<Plugin> pluginsInEnablingState = new HashSet<Plugin>();
112 for (final Plugin plugin : plugins)
113 {
114 try
115 {
116 plugin.enable();
117 if (plugin.getPluginState() == PluginState.ENABLING)
118 {
119 pluginsInEnablingState.add(plugin);
120 }
121 }
122 catch (final RuntimeException ex)
123 {
124 boolean logMsg = pluginExceptionInterception.onEnableException(plugin, ex);
125 if (logMsg)
126 {
127 log.error("Unable to enable plugin " + plugin.getKey(), ex);
128 }
129 }
130 }
131
132 if (!pluginsInEnablingState.isEmpty())
133 {
134
135 WaitUntil.invoke(new WaitUntil.WaitCondition()
136 {
137 private long singlePluginTimeout;
138 private long singlePluginWarn;
139
140 public boolean isFinished()
141 {
142 if (singlePluginTimeout > 0 && singlePluginTimeout < System.currentTimeMillis())
143 {
144 return true;
145 }
146 for (final Iterator<Plugin> i = pluginsInEnablingState.iterator(); i.hasNext();)
147 {
148 final Plugin plugin = i.next();
149 if (plugin.getPluginState() != PluginState.ENABLING)
150 {
151 i.remove();
152 }
153 }
154 if (isAtlassianDevMode() && pluginsInEnablingState.size() == 1)
155 {
156 final long currentTime = System.currentTimeMillis();
157 if (singlePluginTimeout == 0)
158 {
159 log.info("Only one plugin left not enabled. Resetting the timeout to " + (LAST_PLUGIN_TIMEOUT / 1000) + " seconds.");
160
161 singlePluginWarn = currentTime + LAST_PLUGIN_WARN_TIMEOUT;
162 singlePluginTimeout = currentTime + LAST_PLUGIN_TIMEOUT;
163 }
164 else if (singlePluginWarn <= currentTime)
165 {
166
167
168
169
170 final Plugin plugin = pluginsInEnablingState.iterator().next();
171 final long remainingWait = Math.max(0, Math.round((singlePluginTimeout - currentTime) / 1000.0));
172
173 log.warn("Plugin '" + plugin + "' did not enable within " + (LAST_PLUGIN_WARN_TIMEOUT / 1000) + " seconds." + "The plugin should not take this long to enable. Will only attempt to load plugin for another '" + remainingWait + "' seconds.");
174 singlePluginWarn = Long.MAX_VALUE;
175 }
176 }
177 return pluginsInEnablingState.isEmpty();
178 }
179
180 public String getWaitMessage()
181 {
182 return "Plugins that have yet to be enabled: " + pluginsInEnablingState;
183 }
184
185 private boolean isAtlassianDevMode()
186 {
187 return Boolean.getBoolean(PluginUtils.ATLASSIAN_DEV_MODE);
188 }
189 }, PluginUtils.getDefaultEnablingWaitPeriod(), TimeUnit.SECONDS, 1);
190
191
192 if (!pluginsInEnablingState.isEmpty())
193 {
194 final StringBuilder sb = new StringBuilder();
195 for (final Plugin plugin : pluginsInEnablingState)
196 {
197 sb.append(plugin.getKey()).append(',');
198 pluginController.disablePluginWithoutPersisting(plugin.getKey());
199 }
200 sb.deleteCharAt(sb.length() - 1);
201 log.error("Unable to start the following plugins due to timeout while waiting for plugin to enable: " + sb.toString());
202 }
203 }
204 }
205
206
207
208
209
210
211
212 private void scanDependencies(Plugin plugin, Set<String> dependentKeys)
213 {
214 dependentKeys.add(plugin.getKey());
215
216
217 for (String dependencyKey : plugin.getRequiredPlugins())
218 {
219 if (!dependentKeys.contains(dependencyKey) &&
220 (pluginAccessor.getPlugin(dependencyKey) != null) &&
221 !pluginAccessor.isPluginEnabled(dependencyKey))
222 {
223 scanDependencies(pluginAccessor.getPlugin(dependencyKey), dependentKeys);
224 }
225 }
226 }
227 }