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

com.netease.arctic.io.BasicTableTrashManager Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netease.arctic.io;

import com.netease.arctic.table.TableIdentifier;
import com.netease.arctic.utils.TableFileUtils;
import org.apache.hadoop.fs.FileStatus;
import com.netease.arctic.shade.org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
import com.netease.arctic.shade.org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * Basic implementation of {@link TableTrashManager}.
 */
class BasicTableTrashManager implements TableTrashManager {
  private static final Logger LOG = LoggerFactory.getLogger(BasicTableTrashManager.class);
  private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
  private final TableIdentifier tableIdentifier;
  private final ArcticFileIO arcticFileIO;
  private final String tableRootLocation;
  private final String trashLocation;

  BasicTableTrashManager(TableIdentifier tableIdentifier, ArcticFileIO arcticFileIO,
                         String tableRootLocation, String trashLocation) {
    this.tableIdentifier = tableIdentifier;
    this.arcticFileIO = arcticFileIO;
    this.tableRootLocation = tableRootLocation;
    this.trashLocation = trashLocation;
  }

  /**
   * Generate file location in trash
   *
   * @param relativeFileLocation - relative location of file
   * @param trashLocation        - trash location
   * @param deleteTime           - time the file deleted
   * @return file location in trash
   */
  @VisibleForTesting
  static String generateFileLocationInTrash(String relativeFileLocation, String trashLocation,
                                            long deleteTime) {
    return trashLocation + "/" + formatDate(deleteTime) + "/" + relativeFileLocation;
  }

  @VisibleForTesting
  static String getRelativeFileLocation(String tableRootLocation, String fileLocation) {
    tableRootLocation = TableFileUtils.getUriPath(tableRootLocation);
    fileLocation = TableFileUtils.getUriPath(fileLocation);
    if (!tableRootLocation.endsWith("/")) {
      tableRootLocation = tableRootLocation + "/";
    }
    Preconditions.checkArgument(fileLocation.startsWith(tableRootLocation),
        String.format("file %s is not in table location %s", fileLocation, tableRootLocation));
    return fileLocation.replaceFirst(tableRootLocation, "");
  }

  private static String formatDate(long time) {
    LocalDate localDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()).toLocalDate();
    return localDate.format(DATE_FORMATTER);
  }

  private static LocalDate parseDate(String formattedDate) {
    return LocalDate.parse(formattedDate, DATE_FORMATTER);
  }

  @Override
  public TableIdentifier tableId() {
    return tableIdentifier;
  }

  @Override
  public void moveFileToTrash(String path) {
    try {
      Preconditions.checkArgument(!arcticFileIO.isDirectory(path), "should not move a directory to trash " + path);
      String targetFileLocation = generateFileLocationInTrash(
          getRelativeFileLocation(this.tableRootLocation, path),
          this.trashLocation,
          System.currentTimeMillis());
      String targetFileDir = TableFileUtils.getFileDir(targetFileLocation);
      if (!arcticFileIO.exists(targetFileDir)) {
        arcticFileIO.mkdirs(targetFileDir);
      }
      if (arcticFileIO.exists(targetFileLocation)) {
        arcticFileIO.deleteFile(targetFileLocation);
      }
      arcticFileIO.rename(path, targetFileLocation);
    } catch (Exception e) {
      LOG.error("{} failed to move file to trash, {}", tableIdentifier, path, e);
      throw e;
    }
  }

  @Override
  public boolean fileExistInTrash(String path) {
    return findFileFromTrash(path) != null;
  }

  @Override
  public boolean restoreFileFromTrash(String path) {
    String fileFromTrash = findFileFromTrash(path);
    if (fileFromTrash == null) {
      return false;
    }
    try {
      arcticFileIO.rename(fileFromTrash, path);
      return true;
    } catch (Exception e) {
      LOG.info("{} failed to restore file, {}", tableIdentifier, path, e);
      return false;
    }
  }

  @Override
  public void cleanFiles(LocalDate expirationDate) {
    LOG.info("{} start clean files with expiration date {}", tableIdentifier, expirationDate);
    if (!arcticFileIO.exists(this.trashLocation)) {
      return;
    }
    List datePaths = arcticFileIO.list(this.trashLocation);
    if (datePaths.isEmpty()) {
      return;
    }
    for (FileStatus datePath : datePaths) {
      String dateName = TableFileUtils.getFileName(datePath.getPath().toString());
      LocalDate localDate;
      try {
        localDate = parseDate(dateName);
      } catch (Exception e) {
        LOG.warn("{} failed to parse path to date {}", tableIdentifier, datePath.getPath().toString(), e);
        continue;
      }
      if (localDate.compareTo(expirationDate) < 0) {
        arcticFileIO.deleteDirectoryRecursively(datePath.getPath().toString());
        LOG.info("{} delete files in trash for date {} success, {}", tableIdentifier, localDate,
            datePath.getPath().toString());
      } else {
        LOG.info("{} should not delete files in trash for date {},  {}", tableIdentifier, localDate,
            datePath.getPath().toString());
      }
    }
  }

  private String findFileFromTrash(String path) {
    if (!arcticFileIO.exists(this.trashLocation)) {
      return null;
    }
    List datePaths = arcticFileIO.list(this.trashLocation);
    String relativeLocation = getRelativeFileLocation(this.tableRootLocation, path);
    for (FileStatus datePath : datePaths) {
      String fullLocation = datePath.getPath().toString() + "/" + relativeLocation;
      if (arcticFileIO.exists(fullLocation)) {
        if (arcticFileIO.isDirectory(fullLocation)) {
          throw new IllegalArgumentException("can't restore a directory from trash " + fullLocation);
        }
        return fullLocation;
      }
    }
    return null;
  }

  @Override
  public String getTrashLocation() {
    return trashLocation;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy