com.b2international.snowowl.server.console.MaintenanceCommandProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.b2international.snowowl.server.console.MaintenanceCommandProvider.java

Source

/*
 * Copyright 2011-2016 B2i Healthcare Pte Ltd, http://b2i.sg
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.b2international.snowowl.server.console;

import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.slf4j.LoggerFactory;

import com.b2international.commons.StringUtils;
import com.b2international.index.revision.Purge;
import com.b2international.snowowl.core.ApplicationContext;
import com.b2international.snowowl.core.Repositories;
import com.b2international.snowowl.core.RepositoryInfo;
import com.b2international.snowowl.core.SnowOwlApplication;
import com.b2international.snowowl.core.branch.Branch;
import com.b2international.snowowl.core.date.DateFormats;
import com.b2international.snowowl.core.date.Dates;
import com.b2international.snowowl.core.exceptions.NotFoundException;
import com.b2international.snowowl.core.request.SearchResourceRequest.SortField;
import com.b2international.snowowl.datastore.cdo.ICDORepositoryManager;
import com.b2international.snowowl.datastore.commitinfo.CommitInfo;
import com.b2international.snowowl.datastore.commitinfo.CommitInfoDocument;
import com.b2international.snowowl.datastore.request.RepositoryRequests;
import com.b2international.snowowl.datastore.request.repository.OptimizeRequest;
import com.b2international.snowowl.datastore.request.repository.PurgeRequest;
import com.b2international.snowowl.datastore.request.repository.RepositorySearchRequestBuilder;
import com.b2international.snowowl.datastore.server.ServerDbUtils;
import com.b2international.snowowl.datastore.server.migrate.MigrateRequest;
import com.b2international.snowowl.datastore.server.migrate.MigrateRequestBuilder;
import com.b2international.snowowl.datastore.server.migrate.MigrationResult;
import com.b2international.snowowl.datastore.server.reindex.ReindexRequest;
import com.b2international.snowowl.datastore.server.reindex.ReindexRequestBuilder;
import com.b2international.snowowl.datastore.server.reindex.ReindexResult;
import com.b2international.snowowl.eventbus.IEventBus;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;

/**
 * OSGI command contribution with Snow Owl maintenance type commands.
 */
public class MaintenanceCommandProvider implements CommandProvider {

    private static final String DEFAULT_BRANCH_PREFIX = "|--";
    private static final String DEFAULT_INDENT = "   ";

    private static final String LISTBRANCHES_COMMAND = "listbranches";
    private static final String DBCREATEINDEX_COMMAND = "dbcreateindex";
    private static final String REPOSITORIES_COMMAND = "repositories";
    private static final String VERSION_COMMAND = "--version";

    @Override
    public String getHelp() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("---Snow Owl commands---\n");
        buffer.append("\tsnowowl --version - returns the current version\n");
        buffer.append(
                "\tsnowowl dbcreateindex [nsUri] - creates the CDO_CREATED index on the proper DB tables for all classes contained by a package identified by its unique namespace URI.\n");
        buffer.append("\tsnowowl listrepositories - prints all the repositories in the system.\n");
        buffer.append(
                "\tsnowowl listbranches [repository] [branchPath] - prints all the child branches of the specified branch path in the system for a repository. Branch path is MAIN by default and has to be full path (e.g. MAIN/PROJECT/TASK)\n");
        buffer.append(
                "\tsnowowl reindex [repositoryId] [failedCommitTimestamp]- reindexes the content for the given repository ID from the given failed commit timestamp (optional, default timestamp is 1 which means no failed commit).\n");
        buffer.append(
                "\tsnowowl optimize [repositoryId] [maxSegments] - optimizes the underlying index for the repository to have the supplied maximum number of segments (default number is 1)\n");
        buffer.append(
                "\tsnowowl purge [repositoryId] [branchPath] [ALL|LATEST|HISTORY] - optimizes the underlying index by deleting unnecessary documents from the given branch using the given purge strategy (default strategy is LATEST)\n");
        buffer.append("\tsnowowl migrate [repositoryId] [remoteLocation] [-s scriptLocation] [-t commitTimestamp]"
                + " - migrates content from a remote database into the given repository (optionally you can specify a script to run before each"
                + " commit and/or the start commit timestamp). If commitTimestamp is not specified the latest commit of the current dataset will be used \n");
        buffer.append(
                "\tsnowowl repositories [repositoryId] - prints all currently available repositories and their health statuses");
        return buffer.toString();
    }

    /**
     * Reflective template method declaratively registered. Needs to start with
     * "_".
     * 
     * @param interpreter
     * @throws InterruptedException
     */
    public void _snowowl(CommandInterpreter interpreter) throws InterruptedException {
        String cmd = interpreter.nextArgument();
        try {
            if (DBCREATEINDEX_COMMAND.equals(cmd)) {
                createDbIndex(interpreter);
                return;
            }

            if (LISTBRANCHES_COMMAND.equals(cmd)) {
                listBranches(interpreter);
                return;
            }

            if (REPOSITORIES_COMMAND.equals(cmd)) {
                repositories(interpreter);
                return;
            }

            if (VERSION_COMMAND.equals(cmd)) {
                String version = RepositoryRequests.prepareGetServerInfo().buildAsync().execute(getBus()).getSync()
                        .version();
                interpreter.println(version);
                return;
            }

            if ("reindex".equals(cmd)) {
                reindex(interpreter);
                return;
            }

            if ("optimize".equals(cmd)) {
                optimize(interpreter);
                return;
            }

            if ("purge".equals(cmd)) {
                purge(interpreter);
                return;
            }

            if ("migrate".equals(cmd)) {
                migrate(interpreter);
                return;
            }

            interpreter.println(getHelp());
        } catch (Exception ex) {
            LoggerFactory.getLogger("console").error("Failed to execute command", ex);
            if (Strings.isNullOrEmpty(ex.getMessage())) {
                interpreter.println("Something went wrong during the processing of your request.");
            } else {
                interpreter.println(ex.getMessage());
            }
        }
    }

    private static final String COLUMN_FORMAT = "|%-16s|%-16s|%-16s|";

    private void repositories(CommandInterpreter interpreter) {
        final String repositoryId = interpreter.nextArgument();
        RepositorySearchRequestBuilder req = RepositoryRequests.prepareSearch();
        if (!Strings.isNullOrEmpty(repositoryId)) {
            req.one().filterById(repositoryId);
        } else {
            req.all();
        }
        final Repositories repositories = req.buildAsync().execute(getBus()).getSync();

        final int maxDiagLength = ImmutableList.copyOf(repositories).stream().map(RepositoryInfo::diagnosis)
                .map(Strings::nullToEmpty).map(diag -> diag.length()).max(Ints::compare).orElse(16);

        final int maxLength = Math.max(maxDiagLength + 36, 52);

        printSeparator(interpreter, maxLength);
        printHeader(interpreter, "id", "health", Strings.padEnd("diagnosis", maxDiagLength, ' '));
        printSeparator(interpreter, maxLength);
        repositories.forEach(repository -> {
            printLine(interpreter, repository, RepositoryInfo::id, RepositoryInfo::health,
                    repo -> Strings.isNullOrEmpty(repo.diagnosis()) ? "-" : null);
            printSeparator(interpreter, maxLength);
        });
    }

    private void printHeader(final CommandInterpreter interpreter, Object... columns) {
        interpreter.println(String.format(COLUMN_FORMAT, columns));
    }

    private void printSeparator(final CommandInterpreter interpreter, int length) {
        interpreter.println(Strings.repeat("-", length));
    }

    private <T> void printLine(final CommandInterpreter interpreter, T item, Function<T, Object>... values) {
        interpreter.println(String.format(COLUMN_FORMAT,
                Lists.newArrayList(values).stream().map(func -> func.apply(item)).toArray()));
    }

    public synchronized void createDbIndex(CommandInterpreter interpreter) {
        String nsUri = interpreter.nextArgument();
        if (!Strings.isNullOrEmpty(nsUri)) {
            ServerDbUtils.createCdoCreatedIndexOnTables(nsUri);
        } else {
            interpreter.println("Namespace URI should be specified.");
        }
    }

    public synchronized void purge(CommandInterpreter interpreter) {
        final String repositoryId = interpreter.nextArgument();

        if (Strings.isNullOrEmpty(repositoryId)) {
            interpreter.println("RepositoryId parameter is required");
            return;
        }

        final String branchPath = interpreter.nextArgument();

        if (Strings.isNullOrEmpty(branchPath)) {
            interpreter.print("BranchPath parameter is required");
            return;
        }

        final String purgeArg = interpreter.nextArgument();
        final Purge purge = Strings.isNullOrEmpty(purgeArg) ? Purge.LATEST : Purge.valueOf(purgeArg);
        if (purge == null) {
            interpreter.print("Invalid purge parameter. Select one of " + Joiner.on(",").join(Purge.values()));
            return;
        }

        PurgeRequest.builder().setBranchPath(branchPath).setPurge(purge).build(repositoryId).execute(getBus())
                .getSync();
    }

    public synchronized void reindex(CommandInterpreter interpreter) {
        final String repositoryId = interpreter.nextArgument();

        if (Strings.isNullOrEmpty(repositoryId)) {
            interpreter.println("RepositoryId parameter is required");
            return;
        }

        if (!Boolean.getBoolean(SnowOwlApplication.REINDEX_KEY)) {
            interpreter.println(String.format("Please restart Snow Owl with JVM property '-D%s=true'",
                    SnowOwlApplication.REINDEX_KEY));
            return;
        }

        final ReindexRequestBuilder req = ReindexRequest.builder();

        final String failedCommitTimestamp = interpreter.nextArgument();
        if (!StringUtils.isEmpty(failedCommitTimestamp)) {
            req.setFailedCommitTimestamp(Long.parseLong(failedCommitTimestamp));
        }

        final ReindexResult result = req.build(repositoryId).execute(getBus()).getSync();

        interpreter.println(result.getMessage());
    }

    private void migrate(CommandInterpreter interpreter) {
        final String repositoryId = interpreter.nextArgument();

        if (Strings.isNullOrEmpty(repositoryId)) {
            interpreter.println("RepositoryId parameter is required");
            return;
        }

        final String remoteLocation = interpreter.nextArgument();

        if (Strings.isNullOrEmpty(remoteLocation)) {
            interpreter.println("Remote location parameter is required (host:[port])");
            return;
        }

        final MigrateRequestBuilder req = MigrateRequest.builder(remoteLocation);

        Long providedTimestamp = null;

        String nextArg;
        while (!Strings.isNullOrEmpty((nextArg = interpreter.nextArgument()))) {
            final String value = interpreter.nextArgument();
            switch (nextArg) {
            case "-t":
                try {
                    providedTimestamp = Long.parseLong(value);
                } catch (NumberFormatException e) {
                    interpreter.println(String.format(
                            "Error: Invalid commitTimestamp value (was: '%s', expected long number)", value));
                    return;
                }
                break;
            case "-s":
                if (Strings.isNullOrEmpty(value)) {
                    interpreter.println("Error: Path to script is missing");
                    return;
                }
                if (!Paths.get(value).toFile().exists()) {
                    interpreter.println(String.format("Error: Script at '%s' cannot be found", value));
                    return;
                }
                req.setScriptLocation(value);
                break;
            default:
                interpreter.println("Error: Unknown optional parameter " + nextArg);
                return;
            }
        }

        if (providedTimestamp != null) {
            req.setCommitTimestamp(providedTimestamp);
        } else {

            RepositoryRequests.commitInfos().prepareSearchCommitInfo().one()
                    .sortBy(SortField.descending(CommitInfoDocument.Fields.TIME_STAMP)).build(repositoryId)
                    .execute(getBus()).getSync().first().map(CommitInfo::getTimeStamp).map(value -> value + 1)
                    .ifPresent(req::setCommitTimestamp);

        }

        MigrationResult result = req.build(repositoryId).execute(getBus()).getSync();

        interpreter.println(
                String.format("Migration of '%s' repository successfully completed from source '%s'. Result: %s",
                        repositoryId, remoteLocation, result.getMessage()));
    }

    private static IEventBus getBus() {
        return ApplicationContext.getServiceForClass(IEventBus.class);
    }

    public synchronized void optimize(CommandInterpreter interpreter) {
        final String repositoryId = interpreter.nextArgument();
        if (Strings.isNullOrEmpty(repositoryId)) {
            interpreter.println("RepositoryId parameter is required.");
            return;
        }

        // default max segments is 1
        int maxSegments = 1;
        final String maxSegmentsArg = interpreter.nextArgument();
        if (!Strings.isNullOrEmpty(maxSegmentsArg)) {
            maxSegments = Integer.parseInt(maxSegmentsArg);
        }

        interpreter.println("Optimizing index to max. " + maxSegments + " number of segments...");
        OptimizeRequest.builder().setMaxSegments(maxSegments).build(repositoryId).execute(getBus()).getSync();
        interpreter.println("Index optimization completed.");
    }

    public synchronized void listBranches(CommandInterpreter interpreter) {
        String repositoryUUID = interpreter.nextArgument();

        if (isValidRepositoryName(repositoryUUID, interpreter)) {

            String parentBranchPath = interpreter.nextArgument();

            if (Strings.isNullOrEmpty(parentBranchPath)) {
                interpreter.println("Parent branch path was not specified, falling back to MAIN");
                parentBranchPath = Branch.MAIN_PATH;
            } else if (!parentBranchPath.startsWith(Branch.MAIN_PATH)) {
                interpreter.println("Specify parent branch with full path. i.e. MAIN/PROJECT/TASK1");
                return;
            }

            Branch parentBranch = null;

            try {
                parentBranch = RepositoryRequests.branching().prepareGet(parentBranchPath).build(repositoryUUID)
                        .execute(getBus()).getSync(1000, TimeUnit.MILLISECONDS);
            } catch (NotFoundException e) {
                interpreter.println(String.format("Unable to find %s", parentBranchPath));
                return;
            }

            if (parentBranch != null) {
                interpreter.println(String.format("Branch hierarchy for %s in repository %s:", parentBranchPath,
                        repositoryUUID));
                print(parentBranch, getDepthOfBranch(parentBranch), interpreter);
            }

        }
    }

    private void print(final Branch branch, final int parentDepth, CommandInterpreter interpreter) {

        printBranch(branch, getDepthOfBranch(branch) - parentDepth, interpreter);

        List<? extends Branch> children = FluentIterable.from(branch.children()).filter(new Predicate<Branch>() {
            @Override
            public boolean apply(Branch input) {
                return input.parentPath().equals(branch.path());
            }
        }).toSortedList(new Comparator<Branch>() {
            @Override
            public int compare(Branch o1, Branch o2) {
                return Longs.compare(o1.baseTimestamp(), o2.baseTimestamp());
            }
        });

        if (children.size() != 0) {
            for (Branch child : children) {
                print(child, parentDepth, interpreter);
            }
        }

    }

    private void printBranch(Branch branch, int depth, CommandInterpreter interpreter) {
        interpreter.println(String.format("%-30s %-12s B: %s H: %s",
                String.format("%s%s%s", getIndentationForBranch(depth), DEFAULT_BRANCH_PREFIX, branch.name()),
                String.format("[%s]", branch.state()), Dates.formatByGmt(branch.baseTimestamp(), DateFormats.LONG),
                Dates.formatByGmt(branch.headTimestamp(), DateFormats.LONG)));
    }

    private String getIndentationForBranch(int depth) {
        String indent = "";
        for (int i = 0; i < depth; i++) {
            indent += DEFAULT_INDENT;
        }
        return indent;
    }

    private int getDepthOfBranch(Branch currentBranch) {
        return Iterables.size(Splitter.on(Branch.SEPARATOR).split(currentBranch.path()));
    }

    private boolean isValidRepositoryName(String repositoryName, CommandInterpreter interpreter) {
        Set<String> uuidKeySet = getRepositoryManager().uuidKeySet();
        if (!uuidKeySet.contains(repositoryName)) {
            interpreter.println("Could not find repository called: " + repositoryName);
            interpreter.println("Available repository names are: " + uuidKeySet);
            return false;
        }
        return true;
    }

    private ICDORepositoryManager getRepositoryManager() {
        return ApplicationContext.getServiceForClass(ICDORepositoryManager.class);
    }

}