1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.store.migration

File AbstractDataMigrationManager.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart8.png
56% of files have more coverage

Code metrics

78
220
45
5
898
548
99
0.45
4.89
9
2.2

Classes

Class Line # Actions
AbstractDataMigrationManager 59 196 0% 83 67
0.7774086677.7%
AbstractDataMigrationManager.XWikiMigration 84 2 0% 1 0
1.0100%
AbstractDataMigrationManager.ThreadLock 124 10 0% 6 1
0.944444494.4%
AbstractDataMigrationManager.MigrationStatus 175 9 0% 6 6
0.660%
AbstractDataMigrationManager.WikiDeletedEventListener 256 3 0% 3 2
0.666666766.7%
 

Contributing tests

This file is covered by 3 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.store.migration;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.Collections;
26    import java.util.HashMap;
27    import java.util.HashSet;
28    import java.util.List;
29    import java.util.Map;
30    import java.util.Set;
31    import java.util.SortedMap;
32    import java.util.TreeMap;
33   
34    import javax.inject.Inject;
35   
36    import org.slf4j.Logger;
37    import org.xwiki.bridge.event.WikiDeletedEvent;
38    import org.xwiki.component.manager.ComponentLookupException;
39    import org.xwiki.component.manager.ComponentManager;
40    import org.xwiki.component.phase.Initializable;
41    import org.xwiki.component.phase.InitializationException;
42    import org.xwiki.context.Execution;
43    import org.xwiki.context.ExecutionContext;
44    import org.xwiki.job.event.status.JobProgressManager;
45    import org.xwiki.observation.EventListener;
46    import org.xwiki.observation.ObservationManager;
47    import org.xwiki.observation.event.Event;
48   
49    import com.xpn.xwiki.XWikiConfig;
50    import com.xpn.xwiki.XWikiContext;
51    import com.xpn.xwiki.XWikiException;
52   
53    /**
54    * Template for {@link DataMigrationManager}.
55    *
56    * @version $Id: c6c1dd82620ccdaf8b2985bb4fdbba423b201203 $
57    * @since 3.4M1
58    */
 
59    public abstract class AbstractDataMigrationManager implements DataMigrationManager, Initializable
60    {
61    /**
62    * Component manager used to access stores and data migrations.
63    */
64    @Inject
65    protected ComponentManager componentManager;
66   
67    /**
68    * Component manager used to access stores and data migrations.
69    */
70    @Inject
71    protected ObservationManager observationManager;
72   
73    @Inject
74    protected JobProgressManager progress;
75   
76    /**
77    * Ordered list of migrators that may be applied.
78    */
79    protected Collection<XWikiMigration> migrations;
80   
81    /**
82    * Internal class used to find out the data migration that are being forced in the XWiki configuration file.
83    */
 
84    protected class XWikiMigration
85    {
86    /**
87    * True for a forced migration.
88    */
89    public boolean isForced;
90   
91    /**
92    * The data migration.
93    */
94    public DataMigration dataMigration;
95   
96    /**
97    * Build a new XWikiMigration.
98    *
99    * @param dataMigration the data migration
100    * @param isForced true when this migration is forced
101    */
 
102  2852 toggle public XWikiMigration(DataMigration dataMigration, boolean isForced)
103    {
104  2852 this.dataMigration = dataMigration;
105  2852 this.isForced = isForced;
106    }
107    }
108   
109    /**
110    * Logger.
111    */
112    @Inject
113    protected Logger logger;
114   
115    /**
116    * Execution context used to access XWikiContext.
117    */
118    @Inject
119    private Execution execution;
120   
121    /**
122    * Internal class used to prevent double checking of the database during migration operation.
123    */
 
124    private static class ThreadLock extends ThreadLocal<Integer>
125    {
 
126  859 toggle @Override
127    protected Integer initialValue()
128    {
129  860 return 0;
130    }
131   
132    /**
133    * Release the lock.
134    */
 
135  680158 toggle public void unlock()
136    {
137  680203 int i = get();
138  680207 if (i > 0) {
139  680196 set(--i);
140    }
141    }
142   
143    /**
144    * Acquire the lock.
145    */
 
146  453469 toggle public void lock()
147    {
148  453513 set(get() + 1);
149    }
150   
151    /**
152    * Try to acquire the lock.
153    *
154    * @return true if the lock has been acquired
155    */
 
156  227525 toggle public boolean tryLock()
157    {
158  227545 int i = get();
159  227551 if (i > 0) {
160  853 return false;
161    }
162  226685 set(++i);
163  226689 return true;
164    }
165    }
166   
167    /**
168    * Semaphore to prevent re-entrance.
169    */
170    private final ThreadLock lock = new ThreadLock();
171   
172    /**
173    * Internal class used to store the migration status of the database.
174    */
 
175    private static class MigrationStatus implements DataMigrationStatus
176    {
177    /**
178    * Current version of the database.
179    */
180    private XWikiDBVersion version;
181   
182    /**
183    * True if the database has been migrated during the current run.
184    */
185    private Exception migrationException;
186   
187    /**
188    * The exception produced by a migration failure, if any.
189    */
190    private boolean migrationAttempted;
191   
192    /**
193    * Build a simple status with no migration attempted, just storing the current database version.
194    *
195    * @param version the current database version
196    */
 
197  81 toggle public MigrationStatus(XWikiDBVersion version)
198    {
199  81 this.version = version;
200  81 this.migrationAttempted = false;
201    }
202   
203    /**
204    * Build a status following a migration storing the current database version and the exception resulting from
205    * the migration. A null value for the exception means a successful migration.
206    *
207    * @param version the current database version
208    * @param migrationException the exception resulting from the migration.
209    */
 
210  15 toggle public MigrationStatus(XWikiDBVersion version, Exception migrationException)
211    {
212  15 this.version = version;
213  15 this.migrationAttempted = true;
214  15 this.migrationException = migrationException;
215    }
216   
 
217  226742 toggle @Override
218    public XWikiDBVersion getDBVersion()
219    {
220  226777 return this.version;
221    }
222   
 
223  0 toggle @Override
224    public boolean hasDataMigrationBeenAttempted()
225    {
226  0 return this.migrationAttempted;
227    }
228   
 
229  0 toggle @Override
230    public boolean hasBeenSuccessfullyMigrated()
231    {
232  0 return this.migrationAttempted && this.migrationException == null;
233    }
234   
 
235  0 toggle @Override
236    public Exception getLastMigrationException()
237    {
238  0 return this.migrationException;
239    }
240    }
241   
242    /**
243    * A cache of wiki database version.
244    */
245    private final Map<String, MigrationStatus> statusCache = new HashMap<String, MigrationStatus>();
246   
247    /**
248    * The final database version when the migration process finishes. This is use to compute the DBVersion of an empty
249    * store and quickly check the outdated status of existing DB.
250    */
251    private XWikiDBVersion targetVersion;
252   
253    /**
254    * Internal class used to clean the database version cache on wiki deletion.
255    */
 
256    private class WikiDeletedEventListener implements EventListener
257    {
 
258  436 toggle @Override
259    public String getName()
260    {
261  436 return "dbversioncache";
262    }
263   
 
264  109 toggle @Override
265    public List<Event> getEvents()
266    {
267  109 return Arrays.<Event>asList(new WikiDeletedEvent());
268    }
269   
 
270  0 toggle @Override
271    public void onEvent(Event event, Object source, Object data)
272    {
273  0 AbstractDataMigrationManager.this.statusCache.remove(((WikiDeletedEvent) event).getWikiId());
274    }
275    }
276   
277    /**
278    * Unified constructor for all subclasses.
279    */
 
280  109 toggle public AbstractDataMigrationManager()
281    {
282    }
283   
284    /**
285    * @return XWikiContext
286    */
 
287  489090 toggle protected XWikiContext getXWikiContext()
288    {
289  489137 ExecutionContext context = this.execution.getContext();
290  489142 return (XWikiContext) context.getProperty("xwikicontext");
291    }
292   
293    /**
294    * @return XWikiConfig to read configuration from xwiki.cfg
295    * @deprecated
296    */
 
297  34921 toggle @Deprecated
298    protected XWikiConfig getXWikiConfig()
299    {
300  34921 return getXWikiContext().getWiki().getConfig();
301    }
302   
303    /**
304    * @deprecated Virtual mode is on by default, starting with XWiki 5.0M2.
305    * @return true if running in virtual mode
306    */
 
307  0 toggle @Deprecated
308    protected boolean isVirtualMode()
309    {
310  0 return true;
311    }
312   
313    /**
314    * @return list of virtual database names
315    * @throws DataMigrationException on error
316    */
 
317  40 toggle @Deprecated
318    protected List<String> getVirtualWikisDatabaseNames() throws DataMigrationException
319    {
320  40 try {
321  40 return getXWikiContext().getWiki().getVirtualWikisDatabaseNames(getXWikiContext());
322    } catch (XWikiException e) {
323  0 throw new DataMigrationException("Unable to retrieve the list of wiki names", e);
324    }
325    }
326   
327    /**
328    * @return the main XWiki database name
329    */
 
330  80 toggle protected String getMainXWiki()
331    {
332  80 return getXWikiContext().getMainXWiki();
333    }
334   
 
335  109 toggle @Override
336    public void initialize() throws InitializationException
337    {
338  109 try {
339  109 SortedMap<XWikiDBVersion, XWikiMigration> availableMigrations = new TreeMap<>();
340   
341  109 Map<XWikiDBVersion, XWikiMigration> forcedMigrations = getForcedMigrations();
342  109 if (!forcedMigrations.isEmpty()) {
343  1 availableMigrations.putAll(forcedMigrations);
344    } else {
345  108 Set<String> ignoredMigrations = new HashSet<String>(Arrays.asList(getXWikiConfig()
346    .getPropertyAsList("xwiki.store.migration.ignored")));
347  108 for (DataMigration migrator : getAllMigrations()) {
348  2852 if (ignoredMigrations.contains(migrator.getClass().getName())
349    || ignoredMigrations.contains(migrator.getVersion().toString()))
350    {
351  1 continue;
352    }
353  2851 XWikiMigration migration = new XWikiMigration(migrator, false);
354  2851 availableMigrations.put(migrator.getVersion(), migration);
355    }
356    }
357   
358  109 this.targetVersion = (availableMigrations.size() > 0) ? availableMigrations.lastKey()
359    : new XWikiDBVersion(0);
360  109 this.migrations = availableMigrations.values();
361    } catch (Exception e) {
362  0 throw new InitializationException("Migration Manager initialization failed", e);
363    }
364   
365  109 this.observationManager.addListener(new WikiDeletedEventListener());
366    }
367   
368    /**
369    * read data version from xwiki.cfg.
370    *
371    * @return data version if set, or null.
372    */
 
373  83 toggle protected XWikiDBVersion getDBVersionFromConfig()
374    {
375  83 String ver = getXWikiConfig().getProperty("xwiki.store.migration.version");
376  83 return ver == null ? null : new XWikiDBVersion(Integer.parseInt(ver));
377    }
378   
379    /**
380    * Read data version from database.
381    *
382    * @return data version or null if this is a new database
383    * @throws DataMigrationException in case of an unexpected error
384    */
 
385  5 toggle protected XWikiDBVersion getDBVersionFromDatabase() throws DataMigrationException
386    {
387  5 return getDBVersionFromConfig();
388    }
389   
 
390  102 toggle @Override
391    public final XWikiDBVersion getDBVersion() throws DataMigrationException
392    {
393  102 this.lock.lock();
394  102 try {
395  102 String wikiName = getXWikiContext().getWikiId();
396  102 MigrationStatus dbStatus = this.statusCache.get(wikiName);
397  102 if (dbStatus == null) {
398  5 synchronized (this.statusCache) {
399  5 XWikiDBVersion version = getDBVersionFromDatabase();
400  5 if (version != null) {
401  2 this.statusCache.put(wikiName, new MigrationStatus(version));
402    }
403  5 return version;
404    }
405    }
406  97 return dbStatus.getDBVersion();
407    } finally {
408  102 this.lock.unlock();
409    }
410    }
411   
 
412  453327 toggle @Override
413    public DataMigrationStatus getDataMigrationStatus() throws DataMigrationException
414    {
415  453370 this.lock.lock();
416  453388 try {
417  453394 String wikiName = getXWikiContext().getWikiId();
418  453374 MigrationStatus dbStatus = this.statusCache.get(wikiName);
419  453374 if (dbStatus == null) {
420  78 synchronized (this.statusCache) {
421  78 XWikiDBVersion version = getDBVersionFromDatabase();
422  78 if (version != null) {
423  42 dbStatus = new MigrationStatus(version);
424  42 this.statusCache.put(wikiName, dbStatus);
425    }
426    }
427    }
428  453356 return dbStatus;
429    } finally {
430  453398 this.lock.unlock();
431    }
432    }
433   
 
434  226731 toggle @Override
435    public final XWikiDBVersion getLatestVersion()
436    {
437  226770 return this.targetVersion;
438    }
439   
 
440  36 toggle @Override
441    public synchronized void initNewDB() throws DataMigrationException
442    {
443  36 this.lock.lock();
444  36 try {
445  36 initializeEmptyDB();
446    } finally {
447  36 this.lock.unlock();
448    }
449    }
450   
451    /**
452    * @throws DataMigrationException if any error
453    */
454    protected abstract void initializeEmptyDB() throws DataMigrationException;
455   
456    /**
457    * @param version to set
458    * @throws DataMigrationException if any error
459    */
460    protected abstract void setDBVersionToDatabase(XWikiDBVersion version) throws DataMigrationException;
461   
462    /**
463    * Update database version and status cache (not after a migration, use updateMigrationStatus).
464    *
465    * @param version database version to be stored
466    * @throws DataMigrationException if any error
467    */
 
468  37 toggle protected final void setDBVersion(XWikiDBVersion version) throws DataMigrationException
469    {
470  37 updateMigrationStatus(version, false, null);
471    }
472   
473    /**
474    * Update database version and status cache after a successful migration.
475    *
476    * @param version new database version
477    * @throws DataMigrationException if any error
478    */
 
479  15 toggle private void updateMigrationStatus(XWikiDBVersion version) throws DataMigrationException
480    {
481  15 updateMigrationStatus(version, true, null);
482    }
483   
484    /**
485    * Update status cache based on last migration failure.
486    *
487    * @param version current database version (last valid version)
488    * @param e exception thrown by the migration
489    * @throws DataMigrationException if any error
490    */
 
491  0 toggle private void updateMigrationStatus(XWikiDBVersion version, Exception e) throws DataMigrationException
492    {
493  0 updateMigrationStatus(version, true, e);
494    }
495   
496    /**
497    * Update database version and status cache based on the new migration status.
498    *
499    * @param version current or new database version
500    * @param migrationAttempted true if this update is the result of a migration process
501    * @param e exception thrown by the last migration or null if the migration was successful
502    * @throws DataMigrationException if any error
503    */
 
504  52 toggle private synchronized void updateMigrationStatus(XWikiDBVersion version, boolean migrationAttempted, Exception e)
505    throws DataMigrationException
506    {
507  52 String wikiName = getXWikiContext().getWikiId();
508  52 if (!migrationAttempted || e == null) {
509  52 setDBVersionToDatabase(version);
510    }
511  52 if (version != null) {
512  52 this.statusCache.put(wikiName,
513  52 (migrationAttempted) ? new MigrationStatus(version, e) : new MigrationStatus(version));
514    }
515    }
516   
517    /**
518    * Update database schema to the latest structure.
519    *
520    * @param migrations the migration that will be executed (since 4.0M1)
521    * @throws DataMigrationException if any error
522    */
523    protected abstract void updateSchema(Collection<XWikiMigration> migrations) throws DataMigrationException;
524   
 
525  227522 toggle @Override
526    public void checkDatabase() throws MigrationRequiredException, DataMigrationException
527    {
528  227541 if (!this.lock.tryLock()) {
529  853 return;
530    }
531  226691 try {
532  226664 if (getDatabaseStatus() == null) {
533  36 initializeCurrentDatabase();
534    }
535   
536    // Proceed with migration (only once)
537  226660 if (this.migrations != null) {
538  34581 tryToProcceedToMigration();
539    }
540   
541  226657 preventAccessToOutdatedDb();
542    } finally {
543  226672 this.lock.unlock();
544    }
545    }
546   
 
547  36 toggle private void initializeCurrentDatabase() throws DataMigrationException
548    {
549  36 try {
550  36 initNewDB();
551    } catch (DataMigrationException e) {
552  0 String message = String.format(
553    "The empty database %s seems to be not writable, please check your configuration!",
554    getXWikiContext().getWikiId());
555  0 this.logger.error(message, e);
556  0 throw new DataMigrationException(message, e);
557    }
558    }
559   
 
560  226665 toggle private DataMigrationStatus getDatabaseStatus() throws DataMigrationException
561    {
562  226690 DataMigrationStatus status;
563  226694 try {
564  226694 status = getDataMigrationStatus();
565    } catch (DataMigrationException e) {
566  0 String message = String.format(
567    "Database %s seems to be inaccessible, please check your configuration!",
568    getXWikiContext().getWikiId());
569  0 this.logger.error(message, e);
570  0 throw new DataMigrationException(message, e);
571    }
572  226663 return status;
573    }
574   
575    /**
576    * Check database status, and throws if access should not be allowed.
577    *
578    * @throws DataMigrationException when a database has failed to migrate.
579    * @throws MigrationRequiredException when a database has not been migrated and require migration.
580    */
 
581  226651 toggle private void preventAccessToOutdatedDb()
582    throws DataMigrationException, MigrationRequiredException
583    {
584  226688 DataMigrationStatus status = getDataMigrationStatus();
585   
586  226655 if (getLatestVersion().compareTo(status.getDBVersion()) > 0) {
587  0 if (status.hasDataMigrationBeenAttempted() && !status.hasBeenSuccessfullyMigrated()) {
588  0 String message = String.format(
589    "Migration of database [%s] has failed, it could not be safely used! Database is currently in"
590    + " version [%d] while the required version is [%d].",
591    getXWikiContext().getWikiId(),
592    status.getDBVersion().getVersion(),
593    getLatestVersion().getVersion());
594  0 throw new DataMigrationException(message, status.getLastMigrationException());
595    } else {
596  0 String message = String.format(
597    "Since database [%s] needs to be migrated, it couldn't be safely used! Please check your"
598    + " configuration to enable required migration for upgrading database from version [%d]"
599    + " to version [%d].",
600    getXWikiContext().getWikiId(),
601    status.getDBVersion().getVersion(),
602    getLatestVersion().getVersion());
603  0 throw new MigrationRequiredException(message);
604    }
605    }
606    }
607   
608    /**
609    * Start migrations if migrations are enabled.
610    *
611    * @throws DataMigrationException
612    */
 
613  34581 toggle private void tryToProcceedToMigration() throws DataMigrationException
614    {
615  34581 XWikiConfig config = getXWikiConfig();
616  34581 if ("1".equals(config.getProperty("xwiki.store.migration", "0"))
617    && !"0".equals(config.getProperty("xwiki.store.hibernate.updateschema"))) {
618    // Run migrations
619  39 this.logger.info("Storage schema updates and data migrations are enabled");
620   
621  39 startMigrationsOnlyOnce();
622   
623    // TODO: Improve or remove this which is inappropriate in a container environment
624  39 if ("1".equals(config.getProperty("xwiki.store.migration.exitAfterEnd", "0"))) {
625  0 this.logger.error("Exiting because xwiki.store.migration.exitAfterEnd is set");
626  0 System.exit(0);
627    }
628    }
629    }
630   
631    /**
632    * Start the migration process only once by synchronization and semaphore.
633    *
634    * @throws DataMigrationException
635    */
 
636  39 toggle private synchronized void startMigrationsOnlyOnce() throws DataMigrationException
637    {
638    // migrations hold available migrations and is used like a semaphore to avoid multiple run
639  39 if (this.migrations == null) {
640  0 return;
641    }
642   
643  39 try {
644  39 startMigrations();
645    } finally {
646  39 this.migrations = null;
647    }
648    }
649   
650    /**
651    * Start the migration process. This one is not thread safe and should be synchronized. The migrations field should
652    * not be null.
653    *
654    * @throws DataMigrationException in case of any error
655    */
 
656  40 toggle protected void startMigrations() throws DataMigrationException
657    {
658  40 Set<String> databasesToMigrate = getDatabasesToMigrate();
659   
660  40 this.progress.pushLevelProgress(databasesToMigrate.size(), this);
661   
662  40 try {
663    // We should migrate the main wiki first to be able to access subwiki descriptors if needed.
664  40 if (!migrateDatabase(getMainXWiki())) {
665  0 String message = "Main wiki database migration failed, it is not safe to continue!";
666  0 this.logger.error(message);
667  0 throw new DataMigrationException(message);
668    }
669   
670  40 int errorCount = 0;
671  40 for (String database : databasesToMigrate) {
672  0 this.progress.startStep(this);
673   
674  0 if (!migrateDatabase(database)) {
675  0 errorCount++;
676    }
677   
678  0 this.progress.endStep(this);
679    }
680   
681  40 if (errorCount > 0) {
682  0 String message = String.format("%s wiki database migration(s) failed.", errorCount);
683  0 this.logger.error(message);
684  0 throw new DataMigrationException(message);
685    }
686    } finally {
687  40 this.progress.popLevelProgress(this);
688    }
689    }
690   
691    /**
692    * Returns the names of the databases that should be migrated. The main wiki database should have been migrated and
693    * is never returned. This is controlled through the "xwiki.store.migration.databases" configuration property in
694    * xwiki.cfg. A value of "all" or no value at all will add all databases. Note that the main database is
695    * automatically added even if not specified.
696    *
697    * @return The names of all databases to migrate.
698    * @throws DataMigrationException if the list of wikis cannot be obtained.
699    */
 
700  40 toggle private Set<String> getDatabasesToMigrate() throws DataMigrationException
701    {
702  40 Set<String> databasesToMigrate = new HashSet<String>();
703   
704    // Add the databases listed by the user (if any). If there's no database name or
705    // a single database named and if it's "all" or "ALL" then automatically add all the registered databases.
706  40 String[] databases = getXWikiConfig().getPropertyAsList("xwiki.store.migration.databases");
707  40 if ((databases.length == 0) || ((databases.length == 1) && databases[0].equalsIgnoreCase("all"))) {
708    // The main wiki will also be included, but, since we are using a Set, it should not be a problem.
709  40 List<String> allwikis = getVirtualWikisDatabaseNames();
710  40 databasesToMigrate.addAll(allwikis);
711    } else {
712  0 Collections.addAll(databasesToMigrate, databases);
713    }
714   
715    // Remove the main wiki if listed since it should have already been migrated.
716  40 databasesToMigrate.remove(getMainXWiki());
717   
718  40 return databasesToMigrate;
719    }
720   
721    /**
722    * Migrate a given database and log error appropriately.
723    *
724    * @param database name of the database to migrate.
725    * @return false if there is an error updating the database.
726    */
 
727  40 toggle private boolean migrateDatabase(String database)
728    {
729  40 XWikiContext context = getXWikiContext();
730   
731    // Save context values so that we can restore them as they were before the migration.
732  40 String currentDatabase = context.getWikiId();
733  40 String currentOriginalDatabase = context.getOriginalWikiId();
734   
735  40 try {
736    // Set up the context so that it points to the virtual wiki corresponding to the
737    // database.
738  40 context.setWikiId(database);
739  40 context.setOriginalWikiId(database);
740   
741  40 Collection<XWikiMigration> neededMigrations = getNeededMigrations();
742  40 updateSchema(neededMigrations);
743  40 startMigrations(neededMigrations);
744    } catch (Exception e) {
745  0 try {
746  0 updateMigrationStatus(getDBVersion(), e);
747    } catch (DataMigrationException e1) {
748    // Should not happen and could be safely ignored.
749    }
750  0 String message = String.format("Failed to migrate database [%s]...", database);
751  0 this.logger.error(message, e);
752  0 return false;
753    } finally {
754  40 context.setWikiId(currentDatabase);
755  40 context.setOriginalWikiId(currentOriginalDatabase);
756    }
757  40 return true;
758    }
759   
760    /**
761    * @return collection of {@link DataMigration} in ascending order, which need be executed.
762    * @throws DataMigrationException if any error
763    */
 
764  43 toggle protected Collection<XWikiMigration> getNeededMigrations() throws DataMigrationException
765    {
766  43 XWikiDBVersion curversion = getDBVersion();
767  43 Collection<XWikiMigration> neededMigrations = new ArrayList<XWikiMigration>();
768   
769  43 for (XWikiMigration migration : this.migrations) {
770  1171 if (migration.isForced || (migration.dataMigration.getVersion().compareTo(curversion) > 0
771    && migration.dataMigration.shouldExecute(curversion)))
772    {
773  18 neededMigrations.add(migration);
774    }
775    }
776   
777  43 if (this.logger.isInfoEnabled()) {
778  43 logNeededMigrationReport(curversion, neededMigrations);
779    }
780   
781  43 return neededMigrations;
782    }
783   
 
784  43 toggle private void logNeededMigrationReport(XWikiDBVersion curversion,
785    Collection<XWikiMigration> neededMigrations)
786    {
787  43 String database = getXWikiContext().getWikiId();
788  43 if (!neededMigrations.isEmpty()) {
789  4 this.logger.info(
790    "The following data migration(s) will be applied for wiki [{}] currently in version [{}]:",
791    database, curversion);
792  4 for (XWikiMigration migration : neededMigrations) {
793  18 this.logger.info(" {} - {}{}", migration.dataMigration.getName(),
794  18 migration.dataMigration.getDescription(), (migration.isForced ? " (forced)" : ""));
795    }
796    } else {
797  39 if (curversion != null) {
798  37 this.logger.info("No data migration to apply for wiki [{}] currently in version [{}]",
799    database, curversion);
800    } else {
801  2 this.logger.info("No data migration to apply for empty wiki [{}]",
802    database);
803    }
804    }
805    }
806   
807    /**
808    * @return a map of forced {@link DataMigration} for this manager
809    * @throws DataMigrationException id any error
810    */
 
811  109 toggle protected Map<XWikiDBVersion, XWikiMigration> getForcedMigrations() throws DataMigrationException
812    {
813  109 SortedMap<XWikiDBVersion, XWikiMigration> forcedMigrations = new TreeMap<XWikiDBVersion, XWikiMigration>();
814  109 for (String hint : getXWikiConfig().getPropertyAsList("xwiki.store.migration.force")) {
815  1 try {
816  1 DataMigration dataMigration = this.componentManager.getInstance(DataMigration.class, hint);
817  1 forcedMigrations.put(dataMigration.getVersion(), new XWikiMigration(dataMigration, true));
818    } catch (ComponentLookupException e) {
819  0 throw new DataMigrationException("Forced dataMigration " + hint + " component could not be found", e);
820    }
821    }
822  109 return forcedMigrations;
823    }
824   
825    /**
826    * @param migrations - run this migrations in order of collection
827    * @throws DataMigrationException if any error
828    */
 
829  40 toggle protected void startMigrations(Collection<XWikiMigration> migrations) throws DataMigrationException
830    {
831  40 XWikiDBVersion curversion = getDBVersion();
832  40 String database = null;
833  40 if (this.logger.isInfoEnabled()) {
834  40 database = getXWikiContext().getWikiId();
835    }
836   
837  40 this.progress.pushLevelProgress(migrations.size(), this);
838   
839  40 try {
840  40 for (XWikiMigration migration : migrations) {
841  15 this.progress.startStep(this);
842   
843  15 if (this.logger.isInfoEnabled()) {
844  15 this.logger.info("Starting data migration [{}] with version [{}] on database [{}]",
845    migration.dataMigration.getName(), migration.dataMigration.getVersion(), database);
846    }
847   
848  15 migration.dataMigration.migrate();
849   
850  15 if (migration.dataMigration.getVersion().compareTo(curversion) > 0) {
851  15 curversion = migration.dataMigration.getVersion();
852  15 updateMigrationStatus(curversion);
853  15 if (this.logger.isInfoEnabled()) {
854  15 this.logger.info(
855    "Data migration [{}] applied successfully, database [{}] upgraded to version [{}]",
856    migration.dataMigration.getName(), database, getDBVersion());
857    }
858  0 } else if (this.logger.isInfoEnabled()) {
859  0 this.logger.info("Data migration [{}] applied successfully, database [{}] stay in version [{}]",
860    migration.dataMigration.getName(), database, getDBVersion());
861    }
862   
863  15 this.progress.endStep(this);
864    }
865    } finally {
866  40 this.progress.popLevelProgress(this);
867    }
868   
869    // If migration is launch on an empty DB or latest migration was unneeded, properly set the latest DB version
870  40 setDatabaseToLastestVersion(curversion);
871    }
872   
873    /**
874    * Set the database to the latest version when migration has all been processed. If migration is launch on an empty
875    * DB or latest migration was unneeded, this method ensure that the database is properly set the latest DB version.
876    *
877    * @param currentVersion the current database version
878    * @throws DataMigrationException if the version update fails
879    */
 
880  40 toggle private void setDatabaseToLastestVersion(XWikiDBVersion currentVersion) throws DataMigrationException
881    {
882  40 if (currentVersion == null) {
883  1 setDBVersion(getLatestVersion());
884  39 } else if (getLatestVersion().compareTo(currentVersion) > 0) {
885  0 updateMigrationStatus(getLatestVersion());
886  0 if (this.logger.isInfoEnabled()) {
887  0 this.logger.info("Database [{}] upgraded to latest version [{}] without needing{} data migration",
888  0 getXWikiContext().getWikiId(), getDBVersion(), (this.migrations.size() > 0) ? " further" : "");
889    }
890    }
891    }
892   
893    /**
894    * @return List of all {@link DataMigration} for this manager
895    * @throws DataMigrationException if any error
896    */
897    protected abstract List<? extends DataMigration> getAllMigrations() throws DataMigrationException;
898    }