View Javadoc

1   /* ------------------------------------
2    * © Kaamelot - 2006
3    * ------------------------------------
4    * Projet  : projectName
5    * Fichier : AUpgradeManager.java
6    * $Id$ 
7    * $Date$ 
8    * $Log$
9    * 
10   */
11  package com.atlassian.jira.upgrade;
12  
13  import java.io.IOException;
14  import java.io.InputStream;
15  import java.util.ArrayList;
16  import java.util.Collection;
17  import java.util.Comparator;
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.SortedMap;
24  import java.util.SortedSet;
25  import java.util.TreeMap;
26  import java.util.TreeSet;
27  
28  import org.apache.commons.lang.exception.ExceptionUtils;
29  import org.apache.log4j.Category;
30  import org.ofbiz.core.entity.GenericEntityException;
31  import org.ofbiz.core.entity.GenericValue;
32  
33  import com.atlassian.core.ofbiz.CoreFactory;
34  import com.atlassian.core.util.ClassLoaderUtils;
35  import com.atlassian.jira.ComponentManager;
36  import com.atlassian.jira.ManagerFactory;
37  import com.atlassian.jira.config.properties.ApplicationProperties;
38  import com.atlassian.jira.upgrade.BuildNumComparator;
39  import com.atlassian.jira.upgrade.UpgradeManager;
40  import com.atlassian.jira.upgrade.UpgradeTask;
41  import com.atlassian.jira.util.JiraUtils;
42  
43  import electric.xml.Document;
44  import electric.xml.Element;
45  import electric.xml.Elements;
46  import electric.xml.ParseException;
47  
48  /**
49   * @author BNP Paribas - SIG/CGI/SFRI - 2005
50   * <b>Description :</b>
51   * @version $Id$
52   * @history <ul>
53   * <li/>Date    		- UserId - Observations
54   * <li/>22 juin 2005 - 139611 - Initialisation de la classe.
55   * </ul>
56   * 
57   */
58  public abstract class AAddOnUpgradeManager implements IAddOnUpgradeManager {
59  
60  	private static Category log = Category.getInstance(AAddOnUpgradeManager.class);
61  	private Comparator buildNumberComp = new BuildNumComparator();
62  
63  	//These maps hold the standard, professional and enterprise upgrades
64  	private SortedMap allAddOnUpgrades = new TreeMap(buildNumberComp);
65  
66  	//This map holds a list of all upgrades that have been done previously
67  	private HashMap upgradeHistoryMap;
68  	
69  //	private String keyParameter;
70  
71  //	private static IAddOnUpgradeManager instance;
72  
73     /**
74      * 
75      */
76     protected AAddOnUpgradeManager() {
77  		addAllUpgrades(getAllAddOnUpgrades(), getAddOnFileName());
78     }
79     
80  	/**
81  	 */
82  	protected AAddOnUpgradeManager(Collection addOnUpgrades) {
83  
84  		for (Iterator iterator = addOnUpgrades.iterator(); iterator.hasNext();)
85  		{
86  			UpgradeTask upgradeTask = (UpgradeTask) iterator.next();
87  			allAddOnUpgrades.put(upgradeTask.getBuildNumber(), upgradeTask);
88  		}
89  	}
90  
91  	public AAddOnUpgradeManager(String addOnFileName)
92  	{
93  		//add all the upgrade tasks in here.
94  		addAllUpgrades(allAddOnUpgrades, addOnFileName);
95  
96  	}
97  
98  	/**
99  	 * Reads an upgrade XML file to get a list of all the upgrades specified in this file and adds these upgrades the upgradeSet accordingly
100 	 * <p/>
101 	 * For every <i>upgrade</i> element, load the class specified by the <i>class</i> element.
102 	 * Put this class into the upgradeSet Map with the <i>build</i> attribute as the key.
103 	 * <p/>
104 	 */
105 	protected void addAllUpgrades(Map upgradeSet, String fileName)
106 	{
107 		InputStream is = ClassLoaderUtils.getResourceAsStream(fileName, this.getClass());
108 		try
109 		{
110 			Document doc = new Document(is);
111 			Element root = doc.getRoot();
112 			Elements actions = root.getElements("upgrade");
113 
114 			while (actions.hasMoreElements())
115 			{
116 				Element action = (Element) actions.nextElement();
117 				String className = action.getElement("class").getTextString();
118 				try
119 				{
120 					UpgradeTask task = (UpgradeTask) JiraUtils.loadComponent(className, this.getClass());
121 
122 					upgradeSet.put(action.getAttribute("build"), task);
123 
124 					//if this task is to be done on setup as well then add it to the setup set as well
125 					if ("true".equals(action.getAttribute("setup")))
126 					{
127 						log.warn("Setup is not supported for AddOn ..." + className);
128 					}
129 				}
130 				catch (Exception e)
131 				{
132 					log.error("Exception loading type: " + className, e);
133 				}
134 			}
135 		}
136 		catch (ParseException e)
137 		{
138 			log.error("Exception: ", e);
139 		}
140 
141 		try
142 		{
143 			is.close();
144 		}
145 		catch (IOException e)
146 		{
147 			log.warn("Could not close " + fileName + " inputStream");
148 		}
149 	}
150 
151 
152 
153 	/**	
154 	 * Gets a set of all the build numbers for which upgrade tasks must be performed. It will
155 	 * only return numbers which are greater than the current JIRA build number
156 	 * <p/>
157 	 * The set will be sorted by ascending build number
158 	 */
159 	private SortedSet getAllRelevantUpgradeBuildNumbers()
160 	{
161 		SortedSet numbers = new TreeSet(buildNumberComp);
162 
163 		Map addOnUpgardes = getRelevantUpgradesFromList(allAddOnUpgrades);
164 
165 		addUpgradeNumbersFromMap(numbers, addOnUpgardes);
166 
167 		return numbers;
168 	}
169 
170 	/**
171 	 * Gets a set of upgrade build numbers for a specified Map of upgradeTasks.
172 	 *
173 	 * @param numbers    This set may be already populated with numbers.
174 	 *                   Any additional numbers from the Map of upgradeTasks will be added to this set
175 	 * @param upgradeMap This is the map of upgradeTasks that the build numbers will be retrieved from
176 	 */
177 	private void addUpgradeNumbersFromMap(SortedSet numbers, Map upgradeMap)
178 	{
179 		Collection upgrades = upgradeMap.entrySet();
180 
181 		for (Iterator iterator = upgrades.iterator(); iterator.hasNext();)
182 		{
183 			Map.Entry entry = (Map.Entry) iterator.next();
184 
185 			numbers.add(entry.getKey());
186 		}
187 		return;
188 	}
189 
190 	/**
191 	 * For each upgrade in the upgradeMap, test whether it is needed (ie upgrade version is greater
192 	 * than the version in the database), and then add to set.
193 	 *
194 	 * @return set of UpgradeTasks that need to be run.
195 	 */
196 	public SortedMap getRelevantUpgradesFromList(Map upgradeMap)
197 	{
198 		try
199 		{
200 			SortedMap unAppliedUpgrades = new TreeMap(buildNumberComp);
201 
202 			for (Iterator iterator = upgradeMap.entrySet().iterator(); iterator.hasNext();)
203 			{
204 				Map.Entry entry = (Map.Entry) iterator.next();
205 
206 				if (needUpgrade((String) entry.getKey()))
207 				{
208 					UpgradeTask upgradeTask = (UpgradeTask) entry.getValue();
209 					unAppliedUpgrades.put(upgradeTask.getBuildNumber(), upgradeTask);
210 				}
211 			}
212 			return unAppliedUpgrades;
213 		}
214 		catch (Throwable e)
215 		{
216 			log.error("Exception getting upgrades " + e.getMessage(), e);
217 			return new TreeMap();
218 		}
219 	}
220 
221 	/** @see com.atlassian.jira.upgrade.AAddOnUpgradeManager#doUpgradeIfNeeded()
222 	 */
223 	public Collection doUpgradeIfNeeded() {
224 	   String msg;
225 		Collection errors = new ArrayList();
226 		if (needUpgrade()) {
227 			if (isUpgradable()) {
228 				log.info("Detected that an upgrade is needed; existing data at build " + getAddOnBuildNumber());
229 				errors = doUpgrade();
230 			} else {
231 			   msg = "Detected that an upgrade is needed, but upgradable due to insufficient Jira Build";
232 				log.debug(msg);
233 				errors.add(msg);
234 			}
235 		} else {
236 		   msg = "Detected that no upgrade is neccessary";
237 			log.debug(msg);
238 			errors.add(msg);
239 		}
240 
241 		return errors;
242 	}
243 
244 	/**
245 	 * Gets all the upgrades (AddOn) that need to be run from the build number stored in the database to the current build number
246 	 * <p/>
247 	 * Get the set of upgradeNumbers which are to be performed for this upgrade.
248 	 * <p/>
249 	 * Get the Maps of relevant upgrades for the AddOn using {@link #getRelevantUpgradesFromList}
250 	 * <p/>
251 	 * <p/>
252 	 * Iterate over these numbers and if either of the AddOn maps contains an
253 	 * upgradetask with this number then do the upgrade
254 	 * <p/>
255 	 * If errors are found, it will cancel the upgrade, and list errors to the console.
256 	 * <p/>
257 	 * For each upgrade that happens successfully, it will increment the build number in the
258 	 * database, so that if one fails, you do not have to repeat all the upgrades that have
259 	 * already run.
260 	 * <p/>
261 	 * If there are no errors from the upgrade, the build number in the database is
262 	 * incremented to the current build number.  This is because there may be no upgrades
263 	 * for a particular version & needUpgrade() checks build no in database.
264 	 */
265 	private Collection doUpgrade()
266 	{
267 		log.info("Doing Upgrade...");
268 
269 		Collection errors = new ArrayList();
270 
271 		try
272 		{
273 			Set upgradeNumbers = getAllRelevantUpgradeBuildNumbers();
274 
275 			// get all the relevant upgrades
276 			Map addOnUpgrades = getRelevantUpgradesFromList(allAddOnUpgrades);
277 
278 			errors = runUpgradeTasks(upgradeNumbers, addOnUpgrades);
279 
280 			//if there were no errors then set the build number to the current number
281 			if (errors.isEmpty())
282 			{
283 				// there may not be any patches for this version, so increment to latest build number.
284 				log.info("Upgrade succeeded! Setting data build number to " + getCurrentAddOnBuildNumber());
285 				setAddOnBuildNumber(getCurrentAddOnBuildNumber());
286 			}
287 			else
288 			{
289 				log.error("Errors occurred during upgrade:");
290 				printErrors(errors);
291 			}
292 		}
293 		catch (Throwable e)
294 		{
295 			log.error("Exception thrown during upgrade: " + e.getMessage(), e);
296 			errors.add("Exception thrown during upgrade: " + e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
297 		}
298 
299 		ManagerFactory.globalRefresh();
300 //		ManagerFactory.getCacheManager().flushAll();
301 
302 		return errors;
303 	}
304 
305 	/**
306 	 * Runs the given upgarde tasks for the given build numbers.
307 	 * <p/>
308 	 * The method iterates over the given build numbers and for each runs an upgarde tasks according to the currently installed
309 	 * license.
310 	 * <p/>
311 	 * After all upgrade tasks are run for a build number JIRA's build number is set to its value.
312 	 *
313 	 * @param upgradeNumbers       the build numbers ofr which upgrade tasks need to be run
314 	 * @param addOnUpgrades     standard upgardes to run with build number as key
315 	 * @return a collection of errors that occurred during upgrade (empty collection if no errors occurred)
316 	 * @throws Exception
317 	 */
318 	private Collection runUpgradeTasks(Collection upgradeNumbers, Map addOnUpgrades) throws Exception
319 	{
320 		Collection errors = new ArrayList();
321 
322 		//get a list of any previosly run upgrades so that they are not run again
323 		Map upgradeHistoryMap = getPreviouslyRunUpgrades();
324 
325 		for (Iterator iterator = upgradeNumbers.iterator(); iterator.hasNext();)
326 		{
327 			//get the next upgrade build number
328 			String number = (String) iterator.next();
329 
330 			//if there is a AddOn ugrade for this build then perform it
331 			UpgradeTask addOnUpgradeTask = (UpgradeTask) addOnUpgrades.get(number);
332 
333 			if (!doUpgradeTaskSucess(upgradeHistoryMap, addOnUpgradeTask, errors))
334 			{
335 				break;
336 			}
337 
338 			//if the number of the upgrade is greater than the current build number then set the build number to this number
339 			if (buildNumberComp.compare(number, getAddOnBuildNumber()) > 0)
340 			{
341 				log.info("Setting current build number on to " + number);
342 				setAddOnBuildNumber(number);
343 			}
344 //			ManagerFactory.globalRefresh();
345 
346 		}
347 
348 		return errors;
349 	}
350 
351 	/**
352 	 * Performs an upgrade by executing an Upgrade Task.
353 	 *
354 	 * @return True if the upgrade was performed without errors. False if the upgrade has errors
355 	 * @throws Exception
356 	 */
357 	private boolean doUpgradeTaskSucess(Map upgradeHistoryMap, UpgradeTask upgradeTask, Collection errors) throws Exception
358 	{
359 		if (upgradeTask != null)
360 		{
361 			//if the upgrade has not been run then
362 			if (upgradeHistoryMap.get(upgradeTask.getClass().getName()) == null)
363 			{
364 				log.info("Performing Upgrade Task: " + upgradeTask.getShortDescription());
365 				upgradeTask.doUpgrade();
366 
367 				try
368 				{
369 					addToUpgradeHistory(upgradeTask.getClass());
370 				}
371 				catch (GenericEntityException e)
372 				{
373 					errors.add("There was a problem adding Upgrade Task " + upgradeTask.getShortDescription() + " to the Upgrade History");
374 				}
375 
376 				if (!upgradeTask.getErrors().isEmpty())
377 				{
378 					log.error("Errors during Upgrade Task: " + upgradeTask.getShortDescription());
379 					errors.addAll(upgradeTask.getErrors());
380 					return false;
381 				}
382 
383 				log.info("Upgrade Task: '" + upgradeTask.getShortDescription() + "' succeeded");
384 			}
385 			else
386 				log.info("Upgrade Task: '" + upgradeTask.getShortDescription() + "' has already been run before");
387 		}
388 		return true;
389 	}
390 
391 
392 	public void addToUpgradeHistory(Class upgradeClass) throws GenericEntityException
393 	{
394 //		EntityUtils.createValue("UpgradeHistory", UtilMisc.toMap("upgradeclass", upgradeClass.getName()));
395 	}
396 
397 
398 	/** @see com.atlassian.jira.upgrade.AAddOnUpgradeManager#getAllAddOnUpgrades()
399 	 */
400 	public SortedMap getAllAddOnUpgrades()
401 	{
402 		return allAddOnUpgrades;
403 	}
404 
405 	/**
406 	 * Returns true if the current build number is not equal to the build number in the database.
407 	 * NB - There may not be any upgrades to run.  However, you will need to run doUpgrade() to
408 	 * increment the build number in the database.
409 	 */
410 	public boolean isUpgradable()
411 	{
412 		UpgradeManager manager = (UpgradeManager) ComponentManager.getInstance().getContainer().getComponentInstanceOfType(UpgradeManager.class);
413 		return patchBuildGreaterOrEqualThanCurrent(getRequiredJiraBuildNumber(),manager.getJiraBuildNumber());
414 	}
415 
416 	/**
417 	 * Returns true if the current build number is not equal to the build number in the database.
418 	 * NB - There may not be any upgrades to run.  However, you will need to run doUpgrade() to
419 	 * increment the build number in the database.
420 	 */
421 	public boolean needUpgrade()
422 	{
423 		return !getCurrentAddOnBuildNumber().equals(getAddOnBuildNumber());
424 	}
425 
426 	/**
427 	 * If the patch version is greater than the current version, then return true.  Else return false.
428 	 *
429 	 * @param buildNumber
430 	 * @return
431 	 */
432 	private boolean needUpgrade(String buildNumber)
433 	{
434 		return patchBuildGreaterThanCurrent(getAddOnBuildNumber(), buildNumber);
435 	}
436 	
437 	/**
438 	 * If the patch version is greater than current version, return true.  Else return false
439 	 *
440 	 * @param currentBuild
441 	 * @param patchBuild
442 	 * @return
443 	 */
444 	private boolean patchBuildGreaterThanCurrent(String currentBuild, String patchBuild)
445 	{
446 		return (buildNumberComp.compare(currentBuild, patchBuild) < 0);
447 	}
448 
449 	/**
450 	 * If the patch version is greater than current version, return true.  Else return false
451 	 *
452 	 * @param currentBuild
453 	 * @param patchBuild
454 	 * @return
455 	 */
456 	private boolean patchBuildGreaterOrEqualThanCurrent(String currentBuild, String patchBuild)
457 	{
458 		return (buildNumberComp.compare(currentBuild, patchBuild) <= 0);
459 	}
460 
461 	/**
462 	 * Get the current build number from the database.  This represents the level that this application
463 	 * is patched to.  This may be different to the current version if there are patches waiting to be
464 	 * applied.
465 	 *
466 	 * @return The version information from the database
467 	 */
468 	public String getAddOnBuildNumber()
469 	{
470 		//if null in the database, we need to set to '0' so that it can be compared with
471 		//other versions.
472 		if (ManagerFactory.getApplicationProperties().getString(getKeyParameter()) == null)
473 			setAddOnBuildNumber("0");
474 		return ManagerFactory.getApplicationProperties().getString(getKeyParameter());
475 	}
476 	
477 	public void setAddOnBuildNumber(String version)
478 	{
479 		ApplicationProperties ap = ManagerFactory.getApplicationProperties();
480 		ap.setString(getKeyParameter(), version);
481 	}
482 	
483 	/** Get Current AddOn Build Number provided by release */
484 	public abstract String getCurrentAddOnBuildNumber();
485 
486 	/** Get the Required JIRA Build Number to perform provided Upgrades */
487 	public abstract String getRequiredJiraBuildNumber();
488 
489 	/**
490 	 * Get a list of the Upgrades that have been previously run
491 	 */
492 	private Map getPreviouslyRunUpgrades() throws GenericEntityException
493 	{
494 		//if this list of upgrades has not been retrived then retrieve it otherwise return it
495 		if (upgradeHistoryMap == null)
496 		{
497 			List upgradeHistoryList = CoreFactory.getGenericDelegator().findAll("UpgradeHistory");
498 
499 			upgradeHistoryMap = new HashMap();
500 
501 			for (Iterator it = upgradeHistoryList.iterator(); it.hasNext();)
502 			{
503 				GenericValue upgradeHist = (GenericValue) it.next();
504 				upgradeHistoryMap.put(upgradeHist.getString("upgradeclass"), upgradeHist);
505 			}
506 		}
507 		return upgradeHistoryMap;
508 	}
509 
510 	/**
511 	 * Print errors to log4j at error level
512 	 * @see com.atlassian.jira.upgrade.AAddOnUpgradeManager#printErrors(java.util.Collection)
513 	 * @param errors A collection of strings, describing all the errors that occurred.
514 	 */
515 	public void printErrors(Collection errors)
516 	{
517 		for (Iterator iterator = errors.iterator(); iterator.hasNext();)
518 		{
519 			String s = (String) iterator.next();
520 			log.error("Upgrade Error: " + s);
521 		}
522 	}
523 
524 	/** @return	 */
525 	public abstract String getKeyParameter();
526 	
527 //	protected static Collection logDebug(Collection _errors, final String _msg) {
528 //		log.debug(_msg);
529 //		_errors.add(_msg);
530 //		return _errors;
531 //	}
532 //
533 //	protected static Collection logInfo(Collection _errors, final String _msg) {
534 //		log.info(_msg);
535 //		_errors.add(_msg);
536 //		return _errors;
537 //	}
538 //
539 //	protected static Collection logError(Collection _errors, final String _msg) {
540 //		log.error(_msg);
541 //		_errors.add(_msg);
542 //		return _errors;
543 //	}
544 //
545 //	protected static Collection logWarn(Collection _errors, final String _msg) {
546 //		log.warn(_msg);
547 //		_errors.add(_msg);
548 //		return _errors;
549 //	}
550 
551 }