alluxio-master-2.x/core/server/master/src/main/java/alluxio/master/file/contexts/CreatePathContext.java
/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.master.file.contexts;

import alluxio.Constants;
import alluxio.client.WriteType;
import alluxio.conf.Configuration;
import alluxio.grpc.CreateDirectoryPOptions;
import alluxio.grpc.CreateFilePOptions;
import alluxio.grpc.TtlAction;
import alluxio.grpc.WritePType;
import alluxio.grpc.XAttrPropagationStrategy;
import alluxio.security.authorization.AclEntry;
import alluxio.security.authorization.Mode;
import alluxio.util.CommonUtils;
import alluxio.util.SecurityUtils;

import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.GeneratedMessageV3;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
 * Wrapper for {@link CreateFilePOptions} or {@link CreateDirectoryPOptions} with additional context
 * data.
 *
 * @param <T> Proto Builder type
 * @param <K> Path context type
 */
public abstract class CreatePathContext<T extends GeneratedMessageV3.Builder<?>,
    K extends CreatePathContext<?, ?>> extends OperationContext<T, K> {

  protected boolean mMountPoint;
  protected long mOperationTimeMs;
  protected List<AclEntry> mAcl;
  protected String mOwner;
  protected String mGroup;
  protected boolean mMetadataLoad;
  protected boolean mPersistNonExistingParentDirectories = true;
  private WriteType mWriteType;
  protected Map<String, byte[]> mXAttr;
  protected XAttrPropagationStrategy mXAttrPropStrat;
  @Nullable protected Supplier<String> mMissingDirFingerprint = null;

  //
  // Values for the below fields will be extracted from given proto options
  // No setters are provided for the extracted fields in order to protect consistency
  // between the context and the underlying proto options
  //
  //
  // PS: This extraction can be eliminated by splitting {@link InodeTree.createPath}.
  //
  protected Mode mMode;
  protected boolean mRecursive;
  protected long mTtl;
  protected TtlAction mTtlAction;
  @Nullable protected String mFingerprint;

  /**
   * Creates context with given option data.
   *
   * @param optionsBuilder the options builder
   */
  protected CreatePathContext(T optionsBuilder) {
    super(optionsBuilder);
    mMountPoint = false;
    mOperationTimeMs = System.currentTimeMillis();
    mAcl = Collections.emptyList();
    mMetadataLoad = false;
    mGroup = "";
    mOwner = "";
    mFingerprint = null;
    if (SecurityUtils.isAuthenticationEnabled(Configuration.global())) {
      mOwner = SecurityUtils.getOwnerFromGrpcClient(Configuration.global());
      mGroup = SecurityUtils.getGroupFromGrpcClient(Configuration.global());
    }
    // Initialize mPersisted based on proto write type.
    WritePType writeType = WritePType.NONE;
    mXAttr = null;
    if (optionsBuilder instanceof CreateFilePOptions.Builder) {
      writeType = ((CreateFilePOptions.Builder) optionsBuilder).getWriteType();
      mXAttr = CommonUtils.convertFromByteString(
          ((CreateFilePOptions.Builder) optionsBuilder).getXattrMap());
      mXAttrPropStrat = ((CreateFilePOptions.Builder) optionsBuilder).getXattrPropStrat();
    } else if (optionsBuilder instanceof CreateDirectoryPOptions.Builder) {
      writeType = ((CreateDirectoryPOptions.Builder) optionsBuilder).getWriteType();
      mXAttr = CommonUtils.convertFromByteString(
          ((CreateDirectoryPOptions.Builder) optionsBuilder).getXattrMap());
      mXAttrPropStrat = ((CreateDirectoryPOptions.Builder) optionsBuilder).getXattrPropStrat();
    }
    mWriteType = WriteType.fromProto(writeType);
  }

  private void loadExtractedFields() {
    com.google.protobuf.GeneratedMessageV3.Builder<?> optionsBuilder = getOptions();
    // Populate extracted fields from given options
    if (optionsBuilder instanceof CreateFilePOptions.Builder) {
      CreateFilePOptions.Builder fileOptions = (CreateFilePOptions.Builder) optionsBuilder;
      mMode = Mode.fromProto(fileOptions.getMode());
      mRecursive = fileOptions.getRecursive();
      mTtl = fileOptions.getCommonOptions().getTtl();
      mTtlAction = fileOptions.getCommonOptions().getTtlAction();
    }

    // Populate extracted fields from given options
    if (optionsBuilder instanceof CreateDirectoryPOptions.Builder) {
      CreateDirectoryPOptions.Builder dirOptions = (CreateDirectoryPOptions.Builder) optionsBuilder;
      mMode = Mode.fromProto(dirOptions.getMode());
      mRecursive = dirOptions.getRecursive();
      mTtl = dirOptions.getCommonOptions().getTtl();
      mTtlAction = dirOptions.getCommonOptions().getTtlAction();
    }
  }

  /**
   * @return Mode, extracted from options
   */
  public Mode getMode() {
    loadExtractedFields();
    return mMode;
  }

  /**
   * @return true if persisted, extracted from options
   */
  public boolean isPersisted() {
    loadExtractedFields();
    return mWriteType.isThrough();
  }

  /**
   * @return true if recursive, extracted from options
   */
  public boolean isRecursive() {
    loadExtractedFields();
    return mRecursive;
  }

  /**
   * @return ttl, extracted from options
   */
  public long getTtl() {
    loadExtractedFields();
    return mTtl;
  }

  /**
   * @return ttl action, extracted from options
   */
  public TtlAction getTtlAction() {
    loadExtractedFields();
    return mTtlAction;
  }

  /**
   * Note: This is safe as 'K' extends {@link CreatePathContext}.
   *
   * @return the object's type
   */
  protected K getThis() {
    return (K) this;
  }

  /**
   * @param operationTimeMs the operation time to use
   * @return the updated context
   */
  public K setOperationTimeMs(long operationTimeMs) {
    mOperationTimeMs = operationTimeMs;
    return getThis();
  }

  /**
   * @return the operation time
   */
  public long getOperationTimeMs() {
    return mOperationTimeMs;
  }

  /**
   * Sets an immutable copy of acl as the internal access control list.
   *
   * @param acl the ACL entries
   * @return the updated context
   */
  public K setAcl(List<AclEntry> acl) {
    mAcl = ImmutableList.copyOf(acl);
    return getThis();
  }

  /**
   * @return an immutable list of ACL entries
   */
  public List<AclEntry> getAcl() {
    return mAcl;
  }

  /**
   * @param mountPoint the mount point flag to use; it specifies whether the object to create is a
   *        mount point
   * @return the updated context
   */
  public K setMountPoint(boolean mountPoint) {
    mMountPoint = mountPoint;
    return getThis();
  }

  /**
   * @return the mount point flag; it specifies whether the object to create is a mount point
   */
  public boolean isMountPoint() {
    return mMountPoint;
  }

  /**
   * @param owner the owner to use
   * @return the updated context
   */
  public K setOwner(String owner) {
    mOwner = owner;
    return getThis();
  }

  /**
   * @return the owner
   */
  public String getOwner() {
    return mOwner;
  }

  /**
   * @param group the group to use
   * @return the updated context
   */
  public K setGroup(String group) {
    mGroup = group;
    return getThis();
  }

  /**
   * @return the write type of this path
   */
  public WriteType getWriteType() {
    return mWriteType;
  }

  /**
   * @param writeType type of write on this create
   * @return the updated context
   */
  public K setWriteType(WriteType writeType) {
    mWriteType = writeType;
    return getThis();
  }

  /**
   * @return the group
   */
  public String getGroup() {
    return mGroup;
  }

  /**
   * @param metadataLoad the flag value to use; if true, the created path is a result of a metadata
   *        load
   * @param persistNonExistingParentDirectories if true any non-existing parent directories
   *  will also be created on the UFS (this can only be
   *  set to false if metadataLoad is set to true)
   * @return the updated context
   */
  public K setMetadataLoad(
      boolean metadataLoad, boolean persistNonExistingParentDirectories) {
    Preconditions.checkState(metadataLoad || persistNonExistingParentDirectories);
    mMetadataLoad = metadataLoad;
    mPersistNonExistingParentDirectories = persistNonExistingParentDirectories;
    return getThis();
  }

  /**
   * @return extended attributes on this context
   */
  @Nullable
  public Map<String, byte[]> getXAttr() {
    return mXAttr;
  }

  /**
   * @param xattr extended attributes to set when creating
   * @return the updated context
   */
  public K setXAttr(@Nullable Map<String, byte[]> xattr) {
    mXAttr = xattr;
    return getThis();
  }

  /**
   * @return the fingerprint
   */
  @Nullable
  public String getFingerprint() {
    return mFingerprint;
  }

  /**
   * @param fingerprint the fingerprint
   * @return the updated context
   */
  public K setFingerprint(String fingerprint) {
    mFingerprint = fingerprint;
    return getThis();
  }

  /**
   * @return the fingerprint for missing directories
   */
  public String getMissingDirFingerprint() {
    return mMissingDirFingerprint == null
        ? Constants.INVALID_UFS_FINGERPRINT : mMissingDirFingerprint.get();
  }

  /**
   * @param fingerprint the fingerprint to be used when creating missing nested directories
   * @return the updated context
   */
  public K setMissingDirFingerprint(Supplier<String> fingerprint) {
    mMissingDirFingerprint = fingerprint;
    return getThis();
  }

  /**
   * @return extended attributes propagation strategy of this context
   */
  public XAttrPropagationStrategy getXAttrPropStrat() {
    return mXAttrPropStrat;
  }

  /**
   * @return true if non-existing parent directories should be persisted,
   * can only be false if the metadataLoad flag is true
   */
  public boolean isPersistNonExistingParentDirectories() {
    return mPersistNonExistingParentDirectories;
  }

  /**
   * @return the metadataLoad flag; if true, the create path is a result of a metadata load
   */
  public boolean isMetadataLoad() {
    return mMetadataLoad;
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("ProtoOptions", getOptions().build())
        .add("MountPoint", mMountPoint)
        .add("Acl", mAcl)
        .add("Owner", mOwner)
        .add("Group", mGroup)
        .add("MetadataLoad", mMetadataLoad)
        .add("writeType", mWriteType)
        .add("xattr", mXAttr)
        .add("Fingerprint", mFingerprint)
        .toString();
  }
}
