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

org.projectnessie.versioned.transfer.ExportCommon Maven / Gradle / Ivy

There is a newer version: 0.97.1
Show newest version
/*
 * Copyright (C) 2022 Dremio
 *
 * 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 org.projectnessie.versioned.transfer;

import static org.projectnessie.versioned.storage.common.objtypes.StandardObjType.UNIQUE;
import static org.projectnessie.versioned.transfer.ExportImportConstants.EXPORT_METADATA;
import static org.projectnessie.versioned.transfer.ExportImportConstants.HEADS_AND_FORKS;
import static org.projectnessie.versioned.transfer.ExportImportConstants.REPOSITORY_DESCRIPTION;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.projectnessie.api.NessieVersion;
import org.projectnessie.nessie.relocated.protobuf.UnsafeByteOperations;
import org.projectnessie.versioned.storage.common.logic.RepositoryDescription;
import org.projectnessie.versioned.storage.common.objtypes.CustomObjType;
import org.projectnessie.versioned.storage.common.objtypes.UniqueIdObj;
import org.projectnessie.versioned.storage.common.objtypes.UpdateableObj;
import org.projectnessie.versioned.storage.common.persist.Obj;
import org.projectnessie.versioned.storage.common.persist.ObjId;
import org.projectnessie.versioned.storage.common.persist.ObjType;
import org.projectnessie.versioned.storage.serialize.SmileSerialization;
import org.projectnessie.versioned.transfer.files.ExportFileSupplier;
import org.projectnessie.versioned.transfer.related.TransferRelatedObjects;
import org.projectnessie.versioned.transfer.serialize.TransferTypes;
import org.projectnessie.versioned.transfer.serialize.TransferTypes.ExportMeta;
import org.projectnessie.versioned.transfer.serialize.TransferTypes.ExportVersion;
import org.projectnessie.versioned.transfer.serialize.TransferTypes.HeadsAndForks;
import org.projectnessie.versioned.transfer.serialize.TransferTypes.RelatedObj;
import org.projectnessie.versioned.transfer.serialize.TransferTypes.RepositoryDescriptionProto;

abstract class ExportCommon {
  final ExportFileSupplier exportFiles;
  final NessieExporter exporter;
  final TransferRelatedObjects transferRelatedObjects;
  final ExportVersion exportVersion;

  private Batcher genericObjBatcher;

  ExportCommon(
      ExportFileSupplier exportFiles, NessieExporter exporter, ExportVersion exportVersion) {
    this.exportFiles = exportFiles;
    this.exporter = exporter;

    this.exportVersion = verifyExportVersion(exportVersion);

    this.transferRelatedObjects =
        new CompositeTransferRelatedObjects(exporter.genericObjectResolvers());
  }

  private static ExportVersion verifyExportVersion(ExportVersion exportVersion) {
    if (exportVersion == null) {
      exportVersion = ExportVersion.UNRECOGNIZED;
    }
    switch (exportVersion) {
      case V1:
        throw new IllegalArgumentException(
            "Cannot export using export-version " + exportVersion.getNumber());
      case V2:
      case V3:
        break;
      default:
        throw new IllegalArgumentException(
            "Unimplemented export-version " + exportVersion.getNumber());
    }

    return exportVersion;
  }

  ExportMeta exportRepo() throws IOException {
    ExportContext exportContext = createExportContext(getExportVersion());
    try {
      // Generic objects are written by multiple export tasks, so we need to open (and close) its
      // `Batcher` instance here.
      try (Batcher genericObjBatcher =
          new Batcher<>(exporter.commitBatchSize(), objs -> mapGenericObjs(objs, exportContext))) {
        this.genericObjBatcher = genericObjBatcher;

        exporter.progressListener().progress(ProgressEvent.STARTED);
        prepare(exportContext);

        exporter.progressListener().progress(ProgressEvent.START_COMMITS);
        HeadsAndForks headsAndForks = exportCommits(exportContext);
        exportContext.commitOutput.finishCurrentFile();
        writeHeadsAndForks(headsAndForks);
        exporter.progressListener().progress(ProgressEvent.END_COMMITS);

        exporter.progressListener().progress(ProgressEvent.START_NAMED_REFERENCES);
        exportReferences(exportContext);
        exportContext.namedReferenceOutput.finishCurrentFile();
        exporter.progressListener().progress(ProgressEvent.END_NAMED_REFERENCES);

        exporter.progressListener().progress(ProgressEvent.START_META);
        writeRepositoryInformation();

      } finally {
        this.genericObjBatcher = null;
      }

      // Must close the `Batcher` before `ExportContext.finish()` to have the right values in
      // `ExportMeta`.

      ExportMeta meta = exportContext.finish();
      writeExportMeta(meta);
      exporter.progressListener().progress(ProgressEvent.END_META, meta);

      exporter.progressListener().progress(ProgressEvent.FINISHED);

      return meta;
    } finally {
      // Ensure that all output streams are closed.
      exportContext.closeSilently();
    }
  }

  final long currentTimestampMillis() {
    return exporter.clock().millis();
  }

  ExportContext createExportContext(ExportVersion exportVersion) {
    return new ExportContext(
        exportFiles,
        exporter,
        ExportMeta.newBuilder()
            .setNessieVersion(NessieVersion.NESSIE_VERSION)
            .setCreatedMillisEpoch(currentTimestampMillis())
            .setVersion(exportVersion));
  }

  ExportVersion getExportVersion() {
    return exportVersion;
  }

  abstract void prepare(ExportContext exportContext);

  abstract void exportReferences(ExportContext exportContext);

  abstract HeadsAndForks exportCommits(ExportContext exportContext);

  void writeRepositoryInformation() throws IOException {
    handleGenericObjs(transferRelatedObjects.repositoryRelatedObjects());

    RepositoryDescription repositoryDescription =
        exporter.repositoryLogic().fetchRepositoryDescription();
    if (repositoryDescription != null) {
      writeRepositoryDescription(
          TransferTypes.RepositoryDescriptionProto.newBuilder()
              .putAllProperties(repositoryDescription.properties())
              .setRepositoryId(exporter.persist().config().repositoryId())
              .setRepositoryCreatedTimestampMillis(
                  repositoryDescription.repositoryCreatedTime().toEpochMilli())
              .setOldestCommitTimestampMillis(
                  repositoryDescription.oldestPossibleCommitTime().toEpochMilli())
              .setDefaultBranchName(repositoryDescription.defaultBranchName())
              .build());
    }
  }

  void writeExportMeta(ExportMeta meta) throws IOException {
    try (OutputStream output = exportFiles.newFileOutput(EXPORT_METADATA)) {
      meta.writeTo(output);
    }
  }

  void writeHeadsAndForks(HeadsAndForks hf) throws IOException {
    try (OutputStream output = exportFiles.newFileOutput(HEADS_AND_FORKS)) {
      hf.writeTo(output);
    }
  }

  void writeRepositoryDescription(RepositoryDescriptionProto repositoryDescription)
      throws IOException {
    try (OutputStream output = exportFiles.newFileOutput(REPOSITORY_DESCRIPTION)) {
      repositoryDescription.writeTo(output);
    }
  }

  void handleGenericObjs(Set ids) {
    if (ids.isEmpty()) {
      return;
    }

    ObjId[] idArray = ids.toArray(ObjId[]::new);
    Obj[] objs = exporter.persist().fetchObjsIfExist(idArray);
    Arrays.stream(objs).filter(Objects::nonNull).forEach(genericObjBatcher::add);
  }

  private void mapGenericObjs(List objs, ExportContext exportContext) {
    for (Obj o : objs) {
      RelatedObj custom = mapGenericObj(o);
      exportContext.writeGeneric(custom);
      // use the same progress value than commits
      exporter.progressListener().progress(ProgressEvent.GENERIC_WRITTEN);
    }
  }

  private RelatedObj mapGenericObj(Obj obj) {
    RelatedObj.Builder genericObj = RelatedObj.newBuilder().setId(obj.id().asBytes());
    ObjType objType = obj.type();
    if (objType.equals(UNIQUE)) {
      UniqueIdObj uniqueIdObj = (UniqueIdObj) obj;
      genericObj
          .setTypeName(UNIQUE.shortName())
          .setSpace(uniqueIdObj.space())
          .setData(uniqueIdObj.value());
    } else if (objType instanceof CustomObjType) {
      // 'obj' can be a 'GenericObj' or, if the actual `ObjType` implementations are on the
      // classpath, the "real" instance, so we need to handle both cases.
      byte[] serialized =
          SmileSerialization.serializeObj(
              obj, compression -> genericObj.setCompression(compression.valueString()));
      genericObj
          .setTypeName(objType.shortName())
          .setEncoding(TransferTypes.Encoding.Smile)
          .setData(UnsafeByteOperations.unsafeWrap(serialized));
      if (obj instanceof UpdateableObj) {
        UpdateableObj updateableObj = (UpdateableObj) obj;
        String versionToken = updateableObj.versionToken();
        if (versionToken != null) {
          genericObj.setVersionToken(versionToken);
        }
      }
    } else {
      throw new IllegalArgumentException("Unhandled object type " + objType.name());
    }
    return genericObj.build();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy