View Javadoc

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