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