View Javadoc

1   package com.atlassian.sal.core.upgrade;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.concurrent.TimeUnit;
8   
9   import com.atlassian.beehive.ClusterLock;
10  import com.atlassian.beehive.ClusterLockService;
11  import com.atlassian.plugin.Plugin;
12  import com.atlassian.plugin.PluginAccessor;
13  import com.atlassian.plugin.event.PluginEventListener;
14  import com.atlassian.plugin.event.PluginEventManager;
15  import com.atlassian.plugin.event.events.PluginEnabledEvent;
16  import com.atlassian.sal.api.lifecycle.LifecycleAware;
17  import com.atlassian.sal.api.message.Message;
18  import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
19  import com.atlassian.sal.api.transaction.TransactionCallback;
20  import com.atlassian.sal.api.transaction.TransactionTemplate;
21  import com.atlassian.sal.api.upgrade.PluginUpgradeManager;
22  import com.atlassian.sal.api.upgrade.PluginUpgradeTask;
23  
24  import com.atlassian.sal.core.message.DefaultMessage;
25  import com.google.common.collect.ImmutableList;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.springframework.beans.factory.DisposableBean;
29  import org.springframework.beans.factory.InitializingBean;
30  
31  /**
32   * Processes plugin upgrade operations.
33   * <p/>
34   * Upgrades are triggered by the start lifecycle event, and plugin enabled.
35   */
36  public class DefaultPluginUpgradeManager implements PluginUpgradeManager, LifecycleAware, InitializingBean, DisposableBean
37  {
38      private static final Logger log = LoggerFactory.getLogger(DefaultPluginUpgradeManager.class);
39  
40      protected static final String LOCK_TIMEOUT_PROPERTY = "sal.upgrade.task.lock.timeout";
41      protected static final int LOCK_TIMEOUT_SECONDS = Integer.getInteger(LOCK_TIMEOUT_PROPERTY, 300000);
42  
43      private volatile boolean started = false;
44  
45      private final List<PluginUpgradeTask> upgradeTasks;
46      private final TransactionTemplate transactionTemplate;
47      private final PluginAccessor pluginAccessor;
48      private final PluginSettingsFactory pluginSettingsFactory;
49      private final PluginEventManager pluginEventManager;
50      private final ClusterLockService clusterLockService;
51  
52      public DefaultPluginUpgradeManager(final List<PluginUpgradeTask> upgradeTasks, final TransactionTemplate transactionTemplate,
53                                         final PluginAccessor pluginAccessor, final PluginSettingsFactory pluginSettingsFactory,
54                                         final PluginEventManager pluginEventManager, final ClusterLockService clusterLockService)
55      {
56          this.upgradeTasks = upgradeTasks;
57          this.transactionTemplate = transactionTemplate;
58          this.pluginAccessor = pluginAccessor;
59          this.pluginSettingsFactory = pluginSettingsFactory;
60          this.pluginEventManager = pluginEventManager;
61          this.clusterLockService = clusterLockService;
62      }
63  
64      /**
65       * Notifies the plugin upgrade manager that a plugin update tasks has been registered.
66       * <p/>
67       * This method does nothing but logging at the moment. Is is now deprecated since it could result in circular
68       * dependency when trying to bind already exposed update tasks to plugin manager that is being created.
69       *
70       * @param task  the upgrade task that is being bound
71       * @param props the set of properties that the upgrade task was registered with
72       * @deprecated as of 2.0.16 no longer used we now use the eventing framework to run upgrade tasks for plugins that are 'enabled', see {@link #onPluginEnabled(com.atlassian.plugin.event.events.PluginEnabledEvent)}
73       */
74      @SuppressWarnings("unchecked")
75      @Deprecated
76      public void onBind(final PluginUpgradeTask task, final Map props)
77      {
78          // Doing lots here....
79          log.debug("onbind task = [" + task.getPluginKey() + ", " + task.getBuildNumber() + "] ");
80      }
81  
82      public void onStart()
83      {
84          log.debug("onStart");
85          final List<Message> messages = upgrade();
86  
87          // TODO 1: should do something useful with the messages
88          // TODO 2: we don't know what upgrade tasks these messages came from
89          if (messages != null)
90          {
91              for (final Message msg : messages)
92              {
93                  log.error("Upgrade error: " + msg);
94              }
95          }
96  
97          started = true;
98      }
99  
100     @PluginEventListener
101     public void onPluginEnabled(PluginEnabledEvent event)
102     {
103         // Check if the Application is fully started:
104         if (started)
105         {
106             // Run upgrades for this plugin that as been enabled AFTER the onStart event.
107             final List<Message> messages = upgradeInternal(event.getPlugin());
108             if (messages != null && messages.size() > 0)
109             {
110                 log.error("Error(s) encountered while upgrading plugin '" + event.getPlugin().getName() + "' on enable.");
111                 for (final Message msg : messages)
112                 {
113                     log.error("Upgrade error: " + msg);
114                 }
115             }
116         }
117         // If onStart() has not occurred yet then ignore event - we need to wait until the App is started properly.
118     }
119 
120     /**
121      * @return map of all upgrade tasks (stored by pluginKey)
122      */
123     protected Map<String, List<PluginUpgradeTask>> getUpgradeTasks()
124     {
125         final Map<String, List<PluginUpgradeTask>> pluginUpgrades = new HashMap<String, List<PluginUpgradeTask>>();
126 
127         // Find all implementations of PluginUpgradeTask
128         for (final PluginUpgradeTask upgradeTask : upgradeTasks)
129         {
130             List<PluginUpgradeTask> upgrades = pluginUpgrades.get(upgradeTask.getPluginKey());
131             if (upgrades == null)
132             {
133                 upgrades = new ArrayList<PluginUpgradeTask>();
134                 pluginUpgrades.put(upgradeTask.getPluginKey(), upgrades);
135             }
136             upgrades.add(upgradeTask);
137         }
138 
139         return pluginUpgrades;
140     }
141 
142 
143     @SuppressWarnings("unchecked")
144     public List<Message> upgrade()
145     {
146         return upgradeInternal();
147     }
148 
149     public List<Message> upgradeInternal()
150     {
151         log.info("Running plugin upgrade tasks...");
152 
153         // 1. get all upgrade tasks for all plugins
154         final Map<String, List<PluginUpgradeTask>> pluginUpgrades = getUpgradeTasks();
155 
156 
157         final ArrayList<Message> messages = new ArrayList<Message>();
158 
159         // 2. for each plugin, sort tasks by build number and execute them
160         for (final String pluginKey : pluginUpgrades.keySet())
161         {
162             final List<PluginUpgradeTask> upgrades = pluginUpgrades.get(pluginKey);
163 
164             final List<Message> upgradeMessages = upgradePlugin(pluginKey, upgrades);
165             if (upgradeMessages != null)
166             {
167                 messages.addAll(upgradeMessages);
168             }
169         }
170 
171         return messages;
172     }
173 
174     public List<Message> upgradeInternal(Plugin plugin)
175     {
176         final Map<String, List<PluginUpgradeTask>> pluginUpgrades = getUpgradeTasks();
177         final String pluginKey = plugin.getKey();
178         final List<PluginUpgradeTask> upgrades = pluginUpgrades.get(pluginKey);
179         if (upgrades == null)
180         {
181             // nothing to do
182             return null;
183         }
184         return upgradePlugin(pluginKey, upgrades);
185     }
186 
187     private List<Message> upgradePlugin(final String pluginKey, final List<PluginUpgradeTask> upgrades)
188     {
189         return transactionTemplate.execute(new TransactionCallback<List<Message>>()
190         {
191             @Override
192             public List<Message> doInTransaction()
193             {
194                 final Plugin plugin = pluginAccessor.getPlugin(pluginKey);
195                 if (plugin == null)
196                 {
197                     throw new IllegalArgumentException("Invalid plugin key: " + pluginKey);
198                 }
199 
200                 final PluginUpgrader pluginUpgrader = new PluginUpgrader(plugin, pluginSettingsFactory.createGlobalSettings(), upgrades);
201 
202                 final String lockName = "sal.upgrade." + pluginKey;
203                 final ClusterLock lock = clusterLockService.getLockForName(lockName);
204                 try
205                 {
206                     if (!lock.tryLock(LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS))
207                     {
208                         final String timeoutMessage = "unable to acquire cluster lock named '" + lockName + "' after waiting " + LOCK_TIMEOUT_SECONDS + " seconds; note that this timeout may be adjusted via the system property '" + LOCK_TIMEOUT_PROPERTY + "'";
209                         log.error(timeoutMessage);
210                         return ImmutableList.<Message>of(new DefaultMessage(timeoutMessage));
211                     }
212                 }
213                 catch (InterruptedException e)
214                 {
215                     final String interruptedMessage = "interrupted while trying to acquire cluster lock named '" + lockName + "' " + e.getMessage();
216                     log.error(interruptedMessage);
217                     return ImmutableList.<Message>of(new DefaultMessage(interruptedMessage));
218                 }
219 
220                 try
221                 {
222                     return pluginUpgrader.upgrade();
223                 }
224                 finally
225                 {
226                     lock.unlock();
227                 }
228             }
229         });
230     }
231 
232     public void afterPropertiesSet() throws Exception
233     {
234         pluginEventManager.register(this);
235     }
236 
237     public void destroy() throws Exception
238     {
239         pluginEventManager.unregister(this);
240     }
241 }