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

net.ravendb.client.documents.smuggler.DatabaseSmuggler Maven / Gradle / Ivy

package net.ravendb.client.documents.smuggler;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import net.ravendb.client.documents.IDocumentStore;
import net.ravendb.client.documents.commands.GetNextOperationIdCommand;
import net.ravendb.client.documents.operations.Operation;
import net.ravendb.client.exceptions.RavenException;
import net.ravendb.client.http.*;
import net.ravendb.client.json.ContentProviderHttpEntity;
import net.ravendb.client.primitives.Reference;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.FormBodyPartBuilder;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.InputStreamBody;

import java.io.*;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static net.ravendb.client.documents.smuggler.BackupUtils.BACKUP_FILE_SUFFIXES;

public class DatabaseSmuggler {

    private final IDocumentStore _store;
    private final String _databaseName;
    private final RequestExecutor _requestExecutor;

    public DatabaseSmuggler(IDocumentStore store) {
        this(store, null);
    }

    public DatabaseSmuggler(IDocumentStore store, String databaseName) {
        this._store = store;
        this._databaseName = ObjectUtils.firstNonNull(databaseName, store.getDatabase());
        if (_databaseName != null) {
            _requestExecutor = store.getRequestExecutor(_databaseName);
        } else {
            _requestExecutor = null;
        }
    }

    public DatabaseSmuggler forDatabase(String databaseName) {
        if (StringUtils.equalsIgnoreCase(databaseName, _databaseName)) {
            return this;
        }

        return new DatabaseSmuggler(_store, databaseName);
    }

    public Operation exportAsync(DatabaseSmugglerExportOptions options, String toFile) throws IOException {

        File directoryInfo = new File(toFile).getParentFile();
        if (directoryInfo != null && !directoryInfo.exists()) {
            directoryInfo.mkdirs();
        }

        try (FileOutputStream fos = new FileOutputStream(toFile)) {
            return exportAsync(options, response -> {
                try {
                    IOUtils.copyLarge(response, fos);
                } catch (IOException e) {
                    throw new RavenException("Unable to export database: " + e.getMessage(), e);
                }
            });
        }
    }

    private Operation exportAsync(DatabaseSmugglerExportOptions options, Consumer handleStreamResponse) {
        if (options == null) {
            throw new IllegalArgumentException("Options cannot be null");
        }

        if (_requestExecutor == null) {
            throw new IllegalStateException("Cannot use smuggler without a database defined, did you forget to call 'forDatabase'?");
        }

        GetNextOperationIdCommand getOperationIdCommand = new GetNextOperationIdCommand();
        _requestExecutor.execute(getOperationIdCommand);
        Long operationId = getOperationIdCommand.getResult();

        ExportCommand command = new ExportCommand(options, handleStreamResponse, operationId, getOperationIdCommand.getNodeTag());
        _requestExecutor.execute(command);

        return new Operation(_requestExecutor, () -> _store.changes(_databaseName, getOperationIdCommand.getNodeTag()), _requestExecutor.getConventions(), operationId, getOperationIdCommand.getNodeTag());
    }

    public Operation exportAsync(DatabaseSmugglerExportOptions options, DatabaseSmuggler toDatabase) {
        if (options == null) {
            throw new IllegalArgumentException("Options cannot be null");
        }

        if (toDatabase == null) {
            throw new IllegalArgumentException("ToDatabase cannot be null");
        }

        DatabaseSmugglerImportOptions importOptions = new DatabaseSmugglerImportOptions(options);
        Operation result = exportAsync(options, stream -> {
            toDatabase.importAsync(importOptions, stream);
        });

        return result;
    }

    public void importIncrementalAsync(DatabaseSmugglerImportOptions options, String fromDirectory) throws IOException {
        List files = FileUtils.listFiles(new File(fromDirectory), new SuffixFileFilter(BACKUP_FILE_SUFFIXES, IOCase.INSENSITIVE), null)
                .stream()
                .sorted(BackupUtils.COMPARATOR)
                .collect(Collectors.toList());

        if (files.isEmpty()) {
            return;
        }

        EnumSet oldOperateOnTypes = configureOptionsFromIncrementalImport(options);

        for (int i = 0; i < files.size() - 1; i++) {
            File filePath = files.get(i);
            importAsync(options, filePath.getAbsolutePath());
        }

        options.setOperateOnTypes(oldOperateOnTypes);

        File lastFile = files.get(files.size() - 1);
        importAsync(options, lastFile.getAbsolutePath());
    }

    public static EnumSet configureOptionsFromIncrementalImport(DatabaseSmugglerOptions options) {
        options.getOperateOnTypes().add(DatabaseItemType.TOMBSTONES);
        options.getOperateOnTypes().add(DatabaseItemType.COMPARE_EXCHANGE_TOMBSTONES);

        // we import the indexes and Subscriptions from the last file only,
        EnumSet oldOperateOnTypes = options.getOperateOnTypes().clone();

        options.getOperateOnTypes().remove(DatabaseItemType.INDEXES);
        options.getOperateOnTypes().remove(DatabaseItemType.SUBSCRIPTIONS);

        return oldOperateOnTypes;
    }

    public Operation importAsync(DatabaseSmugglerImportOptions options, String fromFile) throws IOException {
        int countOfFileParts = 0;

        Operation result;

        do {
            try (FileInputStream fos = new FileInputStream(fromFile)) {
                result = importAsync(options, fos);
            }
            countOfFileParts++;
            fromFile = String.format("%s.part%3d", fromFile, countOfFileParts);
        } while (new File(fromFile).exists());

        return result;
    }

    public Operation importAsync(DatabaseSmugglerImportOptions options, InputStream stream) {
        if (options == null) {
            throw new IllegalArgumentException("Options cannot be null");
        }

        if (stream == null) {
            throw new IllegalArgumentException("Stream cannot be null");
        }

        if (_requestExecutor == null) {
            throw new IllegalStateException("Cannot use smuggler without a database defined, did you forget to call 'forDatabase'?");
        }

        GetNextOperationIdCommand getOperationIdCommand = new GetNextOperationIdCommand();
        _requestExecutor.execute(getOperationIdCommand);
        Long operationId = getOperationIdCommand.getResult();

        ImportCommand command = new ImportCommand(options, stream, operationId, getOperationIdCommand.getNodeTag());
        _requestExecutor.execute(command);

        return new Operation(_requestExecutor, () -> _store.changes(_databaseName, getOperationIdCommand.getNodeTag()), _requestExecutor.getConventions(), operationId, getOperationIdCommand.getNodeTag());
    }

    private static class ExportCommand extends VoidRavenCommand {
        private final JsonNode _options;
        private final Consumer _handleStreamResponse;
        private final long _operationId;

        public ExportCommand(DatabaseSmugglerExportOptions options,
                             Consumer handleStreamResponse, long operationId, String nodeTag) {
            if (options == null) {
                throw new IllegalArgumentException("Options cannot be null");
            }
            if (handleStreamResponse == null) {
                throw new IllegalArgumentException("HandleStreamResponse cannot be null");
            }
            _handleStreamResponse = handleStreamResponse;
            _options = mapper.valueToTree(options);
            _operationId = operationId;
            selectedNodeTag = nodeTag;
        }

        @Override
        public HttpRequestBase createRequest(ServerNode node, Reference url) {
            url.value = node.getUrl() + "/databases/" + node.getDatabase() + "/smuggler/export?operationId=" + _operationId;

            HttpPost request = new HttpPost();
            ContentProviderHttpEntity entity = new ContentProviderHttpEntity(outputStream -> {
                try (JsonGenerator generator = mapper.getFactory().createGenerator(outputStream)) {
                    generator.getCodec().writeValue(generator, _options);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }, ContentType.APPLICATION_JSON);
            entity.setChunked(true);
            request.setEntity(entity);

            return request;
        }

        @Override
        public ResponseDisposeHandling processResponse(HttpCache cache, CloseableHttpResponse response, String url) {
            try {
                _handleStreamResponse.accept(response.getEntity().getContent());
            } catch (IOException e) {
                throw new RavenException("Unable to export database: " + e.getMessage(), e);
            }

            return ResponseDisposeHandling.AUTOMATIC;
        }
    }

    private static class ImportCommand extends VoidRavenCommand {
        private final JsonNode _options;
        private final InputStream _stream;
        private final long _operationId;

        @Override
        public boolean isReadRequest() {
            return false;
        }

        public ImportCommand(DatabaseSmugglerImportOptions options,
                             InputStream stream, long operationId, String nodeTag) {
            if (stream == null) {
                throw new IllegalArgumentException("Stream cannot be null");
            }
            if (options == null) {
                throw new IllegalArgumentException("Options cannot be null");
            }
            _stream = stream;
            _options = mapper.valueToTree(options);
            _operationId = operationId;
            selectedNodeTag = nodeTag;
        }

        @Override
        public HttpRequestBase createRequest(ServerNode node, Reference url) {
            url.value = node.getUrl() + "/databases/" + node.getDatabase() + "/smuggler/import?operationId=" + _operationId;

            try {
                MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();

                entityBuilder.addBinaryBody("importOptions", mapper.writeValueAsBytes(_options));
                InputStreamBody inputStreamBody = new InputStreamBody(_stream, "name");
                FormBodyPart part = FormBodyPartBuilder.create("file", inputStreamBody)
                        .build();
                entityBuilder.addPart(part);

                HttpPost request = new HttpPost();
                request.setEntity(entityBuilder.build());
                return request;
            } catch (JsonProcessingException e) {
                throw new RavenException("Unable to import database: " + e.getMessage(), e);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy