All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dizitart.no2.migration.MigrationManager Maven / Gradle / Ivy

package org.dizitart.no2.migration;

import org.dizitart.no2.Nitrite;
import org.dizitart.no2.NitriteConfig;
import org.dizitart.no2.collection.Document;
import org.dizitart.no2.common.Fields;
import org.dizitart.no2.common.tuples.Pair;
import org.dizitart.no2.common.tuples.Quartet;
import org.dizitart.no2.common.tuples.Triplet;
import org.dizitart.no2.common.util.SecureString;
import org.dizitart.no2.exceptions.MigrationException;
import org.dizitart.no2.exceptions.NitriteIOException;
import org.dizitart.no2.migration.commands.*;
import org.dizitart.no2.store.StoreMetaData;
import org.dizitart.no2.store.NitriteMap;
import org.dizitart.no2.store.UserAuthenticationService;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;

import static org.dizitart.no2.common.Constants.STORE_INFO;
import static org.dizitart.no2.common.util.ObjectUtils.findRepositoryName;

/**
 * @author Anindya Chatterjee
 * @since 4.0
 */
public class MigrationManager {
    private final NitriteConfig nitriteConfig;
    private final StoreMetaData storeMetadata;
    private final Nitrite database;

    /**
     * Instantiates a new {@link MigrationManager}.
     *
     * @param nitrite the nitrite database
     */
    public MigrationManager(Nitrite nitrite) {
        this.database = nitrite;
        this.nitriteConfig = nitrite.getConfig();
        this.storeMetadata = nitrite.getDatabaseMetaData();
    }

    /**
     * Performs the migration on the database.
     */
    public void doMigrate() {
        if (isMigrationNeeded()) {
            Integer existingVersion = storeMetadata.getSchemaVersion();
            Integer incomingVersion = nitriteConfig.getSchemaVersion();

            Queue migrationPath = findMigrationPath(existingVersion, incomingVersion);
            if (migrationPath == null || migrationPath.isEmpty()) {
                // close the database
                try {
                    database.close();
                } catch (Exception e) {
                    throw new NitriteIOException("Failed to close the database", e);
                }

                throw new MigrationException("Schema version mismatch, no migration path found from version "
                    + storeMetadata.getSchemaVersion() + " to " + nitriteConfig.getSchemaVersion());
            }

            int length = migrationPath.size();
            for (int i = 0; i < length; i++) {
                Migration migration = migrationPath.poll();
                if (migration != null) {
                    Queue migrationSteps = migration.steps();
                    executeMigrationSteps(migrationSteps);
                }
            }
        }
    }

    private boolean isMigrationNeeded() {
        Integer existingVersion = storeMetadata.getSchemaVersion();
        Integer incomingVersion = nitriteConfig.getSchemaVersion();

        if (existingVersion == null) {
            throw new MigrationException("Corrupted database, no version information found");
        }

        if (incomingVersion == null) {
            throw new MigrationException("Invalid version provided");
        }

        return !existingVersion.equals(incomingVersion);
    }

    private Queue findMigrationPath(int start, int end) {
        if (start == end) {
            return new LinkedList<>();
        }

        boolean migrateUp = end > start;
        return findUpMigrationPath(migrateUp, start, end);
    }

    private Queue findUpMigrationPath(boolean upgrade, int start, int end) {
        Queue result = new LinkedList<>();
        while (upgrade ? start < end : start > end) {
            TreeMap targetNodes = nitriteConfig.getMigrations().get(start);
            if (targetNodes == null) {
                return null;
            }

            Set keySet;
            if (upgrade) {
                keySet = targetNodes.descendingKeySet();
            } else {
                keySet = targetNodes.keySet();
            }

            boolean found = false;
            for (int targetVersion : keySet) {
                final boolean shouldAddToPath;
                if (upgrade) {
                    shouldAddToPath = targetVersion <= end && targetVersion > start;
                } else {
                    shouldAddToPath = targetVersion >= end && targetVersion < start;
                }
                if (shouldAddToPath) {
                    result.offer(targetNodes.get(targetVersion));
                    start = targetVersion;
                    found = true;
                    break;
                }
            }
            if (!found) {
                return null;
            }
        }
        return result;
    }

    private void executeMigrationSteps(Queue migrationSteps) {
        if (migrationSteps != null) {
            int length = migrationSteps.size();
            for (int i = 0; i < length; i++) {
                MigrationStep step = migrationSteps.poll();
                executeStep(step);
            }
        }

        StoreMetaData metaData = database.getDatabaseMetaData();
        metaData.setSchemaVersion(nitriteConfig.getSchemaVersion());

        NitriteMap storeInfo = database.getStore().openMap(STORE_INFO,
            String.class, Document.class);
        storeInfo.put(STORE_INFO, metaData.getInfo());
    }

    @SuppressWarnings("unchecked")
    private void executeStep(MigrationStep step) {
        if (step != null) {
            Command command = null;
            switch (step.getInstructionType()) {
                case AddUser:
                    Pair arg1 =
                        (Pair) step.getArguments();
                    command = nitrite -> {
                        UserAuthenticationService authService = new UserAuthenticationService(nitrite.getStore());
                        authService.addOrUpdatePassword(false, arg1.getFirst(),
                            null, arg1.getSecond());
                    };
                    break;

                case ChangePassword:
                    Triplet arg2 =
                        (Triplet) step.getArguments();
                    command = nitrite -> {
                        UserAuthenticationService authService = new UserAuthenticationService(nitrite.getStore());
                        authService.addOrUpdatePassword(true, arg2.getFirst(), arg2.getSecond(), arg2.getThird());
                    };
                    break;

                case DropCollection:
                    String arg3 = (String) step.getArguments();
                    command = new Drop(arg3);
                    break;

                case DropRepository:
                    Pair arg4 = (Pair) step.getArguments();
                    command = new Drop(findRepositoryName(arg4.getFirst(), arg4.getSecond()));
                    break;

                case Custom:
                    CustomInstruction instruction = (CustomInstruction) step.getArguments();
                    command = instruction::perform;
                    break;

                case CollectionRename:
                    Pair arg5 = (Pair) step.getArguments();
                    command = new Rename(arg5.getFirst(), arg5.getSecond());
                    break;

                case CollectionAddField:
                    Triplet arg6 = (Triplet) step.getArguments();
                    command = new AddField(arg6.getFirst(), arg6.getSecond(), arg6.getThird());
                    break;

                case CollectionRenameField:
                    Triplet arg7 = (Triplet) step.getArguments();
                    command = new RenameField(arg7.getFirst(), arg7.getSecond(), arg7.getThird());
                    break;

                case CollectionDeleteField:
                    Pair arg8 = (Pair) step.getArguments();
                    command = new DeleteField(arg8.getFirst(), arg8.getSecond());
                    break;

                case CollectionDropIndex:
                    Pair arg9 = (Pair) step.getArguments();
                    command = new DropIndex(arg9.getFirst(), arg9.getSecond());
                    break;

                case CollectionDropIndices:
                    String collectionName = (String) step.getArguments();
                    command = new DropIndex(collectionName, null);
                    break;

                case CollectionCreateIndex:
                    Triplet arg10 = (Triplet) step.getArguments();
                    command = new CreateIndex(arg10.getFirst(), arg10.getSecond(), arg10.getThird());
                    break;

                case RenameRepository:
                    Quartet arg11 = (Quartet) step.getArguments();
                    String repositoryName = findRepositoryName(arg11.getFirst(), arg11.getSecond());
                    String newRepositoryName = findRepositoryName(arg11.getThird(), arg11.getFourth());
                    command = new Rename(repositoryName, newRepositoryName);
                    break;

                case RepositoryAddField:
                    Quartet arg13
                        = (Quartet) step.getArguments();
                    command = new AddField(findRepositoryName(arg13.getFirst(), arg13.getSecond()),
                        arg13.getThird(), arg13.getFourth());
                    break;

                case RepositoryRenameField:
                    Quartet arg14 =
                        (Quartet) step.getArguments();
                    command = new RenameField(findRepositoryName(arg14.getFirst(), arg14.getSecond()),
                        arg14.getThird(), arg14.getFourth());
                    break;

                case RepositoryDeleteField:
                    Triplet arg15 = (Triplet) step.getArguments();
                    command = new DeleteField(findRepositoryName(arg15.getFirst(), arg15.getSecond()),
                        arg15.getThird());
                    break;

                case RepositoryChangeDataType:
                    Quartet> arg16 =
                        (Quartet>) step.getArguments();
                    command = new ChangeDataType(findRepositoryName(arg16.getFirst(), arg16.getSecond()),
                        arg16.getThird(), arg16.getFourth());
                    break;

                case RepositoryChangeIdField:
                    Quartet arg17 =
                        (Quartet) step.getArguments();
                    command = new ChangeIdField(findRepositoryName(arg17.getFirst(), arg17.getSecond()),
                        arg17.getThird(), arg17.getFourth());
                    break;

                case RepositoryDropIndex:
                    Triplet arg18 = (Triplet) step.getArguments();
                    command = new DropIndex(findRepositoryName(arg18.getFirst(), arg18.getSecond()), arg18.getThird());
                    break;

                case RepositoryDropIndices:
                    Pair arg19 = (Pair) step.getArguments();
                    command = new DropIndex(findRepositoryName(arg19.getFirst(), arg19.getSecond()), null);
                    break;

                case RepositoryCreateIndex:
                    Quartet arg20 =
                        (Quartet) step.getArguments();
                    command = new CreateIndex(findRepositoryName(arg20.getFirst(), arg20.getSecond()),
                        arg20.getThird(), arg20.getFourth());
                    break;
            }

            command.execute(database);
            command.close();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy