1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.store.migration

File AbstractDataMigrationManager.java

 

Coverage histogram

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

Code metrics

78
218
45
5
894
546
99
0.45
4.84
9
2.2

Classes

Class Line # Actions
AbstractDataMigrationManager 59 194 0% 83 81
0.72909772.9%
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 10
0.3333333433.3%
AbstractDataMigrationManager.WikiDeletedEventListener 256 3 0% 3 0
1.0100%
 

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: 376d0ddb106b988518536122417937eff38b93fc $
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  1552 toggle public XWikiMigration(DataMigration dataMigration, boolean isForced)
103    {
104  1552 this.dataMigration = dataMigration;
105  1552 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  390 toggle @Override
127    protected Integer initialValue()
128    {
129  390 return 0;
130    }
131   
132    /**
133    * Release the lock.
134    */
 
135  124526 toggle public void unlock()
136    {
137  124521 int i = get();
138  124532 if (i > 0) {
139  124538 set(--i);
140    }
141    }
142   
143    /**
144    * Acquire the lock.
145    */
 
146  83039 toggle public void lock()
147    {
148  83043 set(get() + 1);
149    }
150   
151    /**
152    * Try to acquire the lock.
153    *
154    * @return true if the lock has been acquired
155    */
 
156  41938 toggle public boolean tryLock()
157    {
158  41936 int i = get();
159  41934 if (i > 0) {
160  452 return false;
161    }
162  41480 set(++i);
163  41476 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  67 toggle public MigrationStatus(XWikiDBVersion version)
198    {
199  67 this.version = version;
200  67 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  0 toggle public MigrationStatus(XWikiDBVersion version, Exception migrationException)
211    {
212  0 this.version = version;
213  0 this.migrationAttempted = true;
214  0 this.migrationException = migrationException;
215    }
216   
 
217  41519 toggle @Override
218    public XWikiDBVersion getDBVersion()
219    {
220  41528 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  360 toggle @Override
259    public String getName()
260    {
261  360 return "dbversioncache";
262    }
263   
 
264  90 toggle @Override
265    public List<Event> getEvents()
266    {
267  90 return Arrays.<Event>asList(new WikiDeletedEvent());
268    }
269   
 
270  3 toggle @Override
271    public void onEvent(Event event, Object source, Object data)
272    {
273  3 AbstractDataMigrationManager.this.statusCache.remove(((WikiDeletedEvent) event).getWikiId());
274    }
275    }
276   
277    /**
278    * Unified constructor for all subclasses.
279    */
 
280  90 toggle public AbstractDataMigrationManager()
281    {
282    }
283   
284    /**
285    * @return XWikiContext
286    */
 
287  100519 toggle protected XWikiContext getXWikiContext()
288    {
289  100516 ExecutionContext context = this.execution.getContext();
290  100535 return (XWikiContext) context.getProperty("xwikicontext");
291    }
292   
293    /**
294    * @return XWikiConfig to read configuration from xwiki.cfg
295    * @deprecated
296    */
 
297  16876 toggle @Deprecated
298    protected XWikiConfig getXWikiConfig()
299    {
300  16875 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  33 toggle @Deprecated
318    protected List<String> getVirtualWikisDatabaseNames() throws DataMigrationException
319    {
320  33 try {
321  33 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  66 toggle protected String getMainXWiki()
331    {
332  66 return getXWikiContext().getMainXWiki();
333    }
334   
 
335  90 toggle @Override
336    public void initialize() throws InitializationException
337    {
338  90 try {
339  90 SortedMap<XWikiDBVersion, XWikiMigration> availableMigrations = new TreeMap<>();
340   
341  90 Map<XWikiDBVersion, XWikiMigration> forcedMigrations = getForcedMigrations();
342  90 if (!forcedMigrations.isEmpty()) {
343  1 availableMigrations.putAll(forcedMigrations);
344    } else {
345  89 Set<String> ignoredMigrations = new HashSet<String>(Arrays.asList(getXWikiConfig()
346    .getPropertyAsList("xwiki.store.migration.ignored")));
347  89 for (DataMigration migrator : getAllMigrations()) {
348  1552 if (ignoredMigrations.contains(migrator.getClass().getName())
349    || ignoredMigrations.contains(migrator.getVersion().toString()))
350    {
351  1 continue;
352    }
353  1551 XWikiMigration migration = new XWikiMigration(migrator, false);
354  1551 availableMigrations.put(migrator.getVersion(), migration);
355    }
356    }
357   
358  90 this.targetVersion = (availableMigrations.size() > 0) ? availableMigrations.lastKey()
359    : new XWikiDBVersion(0);
360  90 this.migrations = availableMigrations.values();
361    } catch (Exception e) {
362  0 throw new InitializationException("Migration Manager initialization failed", e);
363    }
364   
365  90 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  69 toggle protected XWikiDBVersion getDBVersionFromConfig()
374    {
375  69 String ver = getXWikiConfig().getProperty("xwiki.store.migration.version");
376  69 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  69 toggle @Override
391    public final XWikiDBVersion getDBVersion() throws DataMigrationException
392    {
393  69 this.lock.lock();
394  69 try {
395  69 String wikiName = getXWikiContext().getWikiId();
396  69 MigrationStatus dbStatus = this.statusCache.get(wikiName);
397  69 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  64 return dbStatus.getDBVersion();
407    } finally {
408  69 this.lock.unlock();
409    }
410    }
411   
 
412  82931 toggle @Override
413    public DataMigrationStatus getDataMigrationStatus() throws DataMigrationException
414    {
415  82933 this.lock.lock();
416  82958 try {
417  82955 String wikiName = getXWikiContext().getWikiId();
418  82971 MigrationStatus dbStatus = this.statusCache.get(wikiName);
419  82960 if (dbStatus == null) {
420  64 synchronized (this.statusCache) {
421  64 XWikiDBVersion version = getDBVersionFromDatabase();
422  64 if (version != null) {
423  26 dbStatus = new MigrationStatus(version);
424  26 this.statusCache.put(wikiName, dbStatus);
425    }
426    }
427    }
428  82957 return dbStatus;
429    } finally {
430  82952 this.lock.unlock();
431    }
432    }
433   
 
434  41528 toggle @Override
435    public final XWikiDBVersion getLatestVersion()
436    {
437  41535 return this.targetVersion;
438    }
439   
 
440  38 toggle @Override
441    public synchronized void initNewDB() throws DataMigrationException
442    {
443  38 this.lock.lock();
444  38 try {
445  38 initializeEmptyDB();
446    } finally {
447  38 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  39 toggle protected final void setDBVersion(XWikiDBVersion version) throws DataMigrationException
469    {
470  39 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  0 toggle private void updateMigrationStatus(XWikiDBVersion version) throws DataMigrationException
480    {
481  0 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  39 toggle private synchronized void updateMigrationStatus(XWikiDBVersion version, boolean migrationAttempted, Exception e)
505    throws DataMigrationException
506    {
507  39 String wikiName = getXWikiContext().getWikiId();
508  39 if (!migrationAttempted || e == null) {
509  39 setDBVersionToDatabase(version);
510    }
511  39 if (version != null) {
512  39 this.statusCache.put(wikiName,
513  39 (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  41941 toggle @Override
526    public void checkDatabase() throws MigrationRequiredException, DataMigrationException
527    {
528  41928 if (!this.lock.tryLock()) {
529  452 return;
530    }
531  41476 try {
532  41480 if (getDatabaseStatus() == null) {
533  38 initializeCurrentDatabase();
534    }
535   
536    // Proceed with migration (only once)
537  41472 if (this.migrations != null) {
538  16595 tryToProcceedToMigration();
539    }
540   
541  41461 preventAccessToOutdatedDb();
542    } finally {
543  41462 this.lock.unlock();
544    }
545    }
546   
 
547  38 toggle private void initializeCurrentDatabase() throws DataMigrationException
548    {
549  38 try {
550  38 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  41479 toggle private DataMigrationStatus getDatabaseStatus() throws DataMigrationException
561    {
562  41478 DataMigrationStatus status;
563  41477 try {
564  41474 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  41472 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  41445 toggle private void preventAccessToOutdatedDb()
582    throws DataMigrationException, MigrationRequiredException
583    {
584  41454 DataMigrationStatus status = getDataMigrationStatus();
585   
586  41472 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  16596 toggle private void tryToProcceedToMigration() throws DataMigrationException
614    {
615  16596 XWikiConfig config = getXWikiConfig();
616  16596 if ("1".equals(config.getProperty("xwiki.store.migration", "0"))
617    && !"0".equals(config.getProperty("xwiki.store.hibernate.updateschema"))) {
618    // Run migrations
619  32 this.logger.info("Storage schema updates and data migrations are enabled");
620   
621  32 startMigrationsOnlyOnce();
622   
623    // TODO: Improve or remove this which is inappropriate in a container environment
624  32 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  32 toggle private synchronized void startMigrationsOnlyOnce() throws DataMigrationException
637    {
638    // migrations hold available migrations and is used like a semaphore to avoid multiple run
639  32 if (this.migrations == null) {
640  0 return;
641    }
642   
643  32 try {
644  32 startMigrations();
645    } finally {
646  32 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  33 toggle protected void startMigrations() throws DataMigrationException
657    {
658  33 Set<String> databasesToMigrate = getDatabasesToMigrate();
659   
660  33 this.progress.pushLevelProgress(databasesToMigrate.size() + 1, this);
661   
662  33 try {
663    // We should migrate the main wiki first to be able to access subwiki descriptors if needed.
664  33 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  33 int errorCount = 0;
671  33 for (String database : databasesToMigrate) {
672  0 this.progress.startStep(this);
673   
674  0 if (!migrateDatabase(database)) {
675  0 errorCount++;
676    }
677    }
678   
679  33 if (errorCount > 0) {
680  0 String message = String.format("%s wiki database migration(s) failed.", errorCount);
681  0 this.logger.error(message);
682  0 throw new DataMigrationException(message);
683    }
684    } finally {
685  33 this.progress.popLevelProgress(this);
686    }
687    }
688   
689    /**
690    * Returns the names of the databases that should be migrated. The main wiki database should have been migrated and
691    * is never returned. This is controlled through the "xwiki.store.migration.databases" configuration property in
692    * xwiki.cfg. A value of "all" or no value at all will add all databases. Note that the main database is
693    * automatically added even if not specified.
694    *
695    * @return The names of all databases to migrate.
696    * @throws DataMigrationException if the list of wikis cannot be obtained.
697    */
 
698  33 toggle private Set<String> getDatabasesToMigrate() throws DataMigrationException
699    {
700  33 Set<String> databasesToMigrate = new HashSet<String>();
701   
702    // Add the databases listed by the user (if any). If there's no database name or
703    // a single database named and if it's "all" or "ALL" then automatically add all the registered databases.
704  33 String[] databases = getXWikiConfig().getPropertyAsList("xwiki.store.migration.databases");
705  33 if ((databases.length == 0) || ((databases.length == 1) && databases[0].equalsIgnoreCase("all"))) {
706    // The main wiki will also be included, but, since we are using a Set, it should not be a problem.
707  33 List<String> allwikis = getVirtualWikisDatabaseNames();
708  33 databasesToMigrate.addAll(allwikis);
709    } else {
710  0 Collections.addAll(databasesToMigrate, databases);
711    }
712   
713    // Remove the main wiki if listed since it should have already been migrated.
714  33 databasesToMigrate.remove(getMainXWiki());
715   
716  33 return databasesToMigrate;
717    }
718   
719    /**
720    * Migrate a given database and log error appropriately.
721    *
722    * @param database name of the database to migrate.
723    * @return false if there is an error updating the database.
724    */
 
725  33 toggle private boolean migrateDatabase(String database)
726    {
727  33 XWikiContext context = getXWikiContext();
728   
729    // Save context values so that we can restore them as they were before the migration.
730  33 String currentDatabase = context.getWikiId();
731  33 String currentOriginalDatabase = context.getOriginalWikiId();
732   
733  33 try {
734    // Set up the context so that it points to the virtual wiki corresponding to the
735    // database.
736  33 context.setWikiId(database);
737  33 context.setOriginalWikiId(database);
738   
739  33 Collection<XWikiMigration> neededMigrations = getNeededMigrations();
740  33 updateSchema(neededMigrations);
741  33 startMigrations(neededMigrations);
742    } catch (Exception e) {
743  0 try {
744  0 updateMigrationStatus(getDBVersion(), e);
745    } catch (DataMigrationException e1) {
746    // Should not happen and could be safely ignored.
747    }
748  0 String message = String.format("Failed to migrate database [%s]...", database);
749  0 this.logger.error(message, e);
750  0 return false;
751    } finally {
752  33 context.setWikiId(currentDatabase);
753  33 context.setOriginalWikiId(currentOriginalDatabase);
754    }
755  33 return true;
756    }
757   
758    /**
759    * @return collection of {@link DataMigration} in ascending order, which need be executed.
760    * @throws DataMigrationException if any error
761    */
 
762  36 toggle protected Collection<XWikiMigration> getNeededMigrations() throws DataMigrationException
763    {
764  36 XWikiDBVersion curversion = getDBVersion();
765  36 Collection<XWikiMigration> neededMigrations = new ArrayList<XWikiMigration>();
766   
767  36 for (XWikiMigration migration : this.migrations) {
768  593 if (migration.isForced || (migration.dataMigration.getVersion().compareTo(curversion) > 0
769    && migration.dataMigration.shouldExecute(curversion)))
770    {
771  3 neededMigrations.add(migration);
772    }
773    }
774   
775  36 if (this.logger.isInfoEnabled()) {
776  36 logNeededMigrationReport(curversion, neededMigrations);
777    }
778   
779  36 return neededMigrations;
780    }
781   
 
782  36 toggle private void logNeededMigrationReport(XWikiDBVersion curversion,
783    Collection<XWikiMigration> neededMigrations)
784    {
785  36 String database = getXWikiContext().getWikiId();
786  36 if (!neededMigrations.isEmpty()) {
787  2 this.logger.info(
788    "The following data migration(s) will be applied for wiki [{}] currently in version [{}]:",
789    database, curversion);
790  2 for (XWikiMigration migration : neededMigrations) {
791  3 this.logger.info(" {} - {}{}", migration.dataMigration.getName(),
792  3 migration.dataMigration.getDescription(), (migration.isForced ? " (forced)" : ""));
793    }
794    } else {
795  34 if (curversion != null) {
796  32 this.logger.info("No data migration to apply for wiki [{}] currently in version [{}]",
797    database, curversion);
798    } else {
799  2 this.logger.info("No data migration to apply for empty wiki [{}]",
800    database);
801    }
802    }
803    }
804   
805    /**
806    * @return a map of forced {@link DataMigration} for this manager
807    * @throws DataMigrationException id any error
808    */
 
809  90 toggle protected Map<XWikiDBVersion, XWikiMigration> getForcedMigrations() throws DataMigrationException
810    {
811  90 SortedMap<XWikiDBVersion, XWikiMigration> forcedMigrations = new TreeMap<XWikiDBVersion, XWikiMigration>();
812  90 for (String hint : getXWikiConfig().getPropertyAsList("xwiki.store.migration.force")) {
813  1 try {
814  1 DataMigration dataMigration = this.componentManager.getInstance(DataMigration.class, hint);
815  1 forcedMigrations.put(dataMigration.getVersion(), new XWikiMigration(dataMigration, true));
816    } catch (ComponentLookupException e) {
817  0 throw new DataMigrationException("Forced dataMigration " + hint + " component could not be found", e);
818    }
819    }
820  90 return forcedMigrations;
821    }
822   
823    /**
824    * @param migrations - run this migrations in order of collection
825    * @throws DataMigrationException if any error
826    */
 
827  33 toggle protected void startMigrations(Collection<XWikiMigration> migrations) throws DataMigrationException
828    {
829  33 XWikiDBVersion curversion = getDBVersion();
830  33 String database = null;
831  33 if (this.logger.isInfoEnabled()) {
832  33 database = getXWikiContext().getWikiId();
833    }
834   
835  33 this.progress.pushLevelProgress(migrations.size(), this);
836   
837  33 try {
838  33 for (XWikiMigration migration : migrations) {
839  0 this.progress.startStep(this);
840   
841  0 if (this.logger.isInfoEnabled()) {
842  0 this.logger.info("Starting data migration [{}] with version [{}] on database [{}]",
843    migration.dataMigration.getName(), migration.dataMigration.getVersion(), database);
844    }
845   
846  0 migration.dataMigration.migrate();
847   
848  0 if (migration.dataMigration.getVersion().compareTo(curversion) > 0) {
849  0 curversion = migration.dataMigration.getVersion();
850  0 updateMigrationStatus(curversion);
851  0 if (this.logger.isInfoEnabled()) {
852  0 this.logger.info(
853    "Data migration [{}] applied successfully, database [{}] upgraded to version [{}]",
854    migration.dataMigration.getName(), database, getDBVersion());
855    }
856  0 } else if (this.logger.isInfoEnabled()) {
857  0 this.logger.info("Data migration [{}] applied successfully, database [{}] stay in version [{}]",
858    migration.dataMigration.getName(), database, getDBVersion());
859    }
860    }
861    } finally {
862  33 this.progress.popLevelProgress(this);
863    }
864   
865    // If migration is launch on an empty DB or latest migration was unneeded, properly set the latest DB version
866  33 setDatabaseToLastestVersion(curversion);
867    }
868   
869    /**
870    * Set the database to the latest version when migration has all been processed. If migration is launch on an empty
871    * DB or latest migration was unneeded, this method ensure that the database is properly set the latest DB version.
872    *
873    * @param currentVersion the current database version
874    * @throws DataMigrationException if the version update fails
875    */
 
876  33 toggle private void setDatabaseToLastestVersion(XWikiDBVersion currentVersion) throws DataMigrationException
877    {
878  33 if (currentVersion == null) {
879  1 setDBVersion(getLatestVersion());
880  32 } else if (getLatestVersion().compareTo(currentVersion) > 0) {
881  0 updateMigrationStatus(getLatestVersion());
882  0 if (this.logger.isInfoEnabled()) {
883  0 this.logger.info("Database [{}] upgraded to latest version [{}] without needing{} data migration",
884  0 getXWikiContext().getWikiId(), getDBVersion(), (this.migrations.size() > 0) ? " further" : "");
885    }
886    }
887    }
888   
889    /**
890    * @return List of all {@link DataMigration} for this manager
891    * @throws DataMigrationException if any error
892    */
893    protected abstract List<? extends DataMigration> getAllMigrations() throws DataMigrationException;
894    }