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

org.apache.gravitino.client.GravitinoMetalake Maven / Gradle / Ivy

Go to download

Gravitino is a high-performance, geo-distributed and federated metadata lake.

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 org.apache.gravitino.client;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.CatalogChange;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsCatalogs;
import org.apache.gravitino.authorization.Group;
import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.authorization.Role;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.authorization.SupportsRoles;
import org.apache.gravitino.authorization.User;
import org.apache.gravitino.dto.AuditDTO;
import org.apache.gravitino.dto.MetalakeDTO;
import org.apache.gravitino.dto.authorization.SecurableObjectDTO;
import org.apache.gravitino.dto.requests.CatalogCreateRequest;
import org.apache.gravitino.dto.requests.CatalogUpdateRequest;
import org.apache.gravitino.dto.requests.CatalogUpdatesRequest;
import org.apache.gravitino.dto.requests.GroupAddRequest;
import org.apache.gravitino.dto.requests.OwnerSetRequest;
import org.apache.gravitino.dto.requests.PrivilegeGrantRequest;
import org.apache.gravitino.dto.requests.PrivilegeRevokeRequest;
import org.apache.gravitino.dto.requests.RoleCreateRequest;
import org.apache.gravitino.dto.requests.RoleGrantRequest;
import org.apache.gravitino.dto.requests.RoleRevokeRequest;
import org.apache.gravitino.dto.requests.TagCreateRequest;
import org.apache.gravitino.dto.requests.TagUpdateRequest;
import org.apache.gravitino.dto.requests.TagUpdatesRequest;
import org.apache.gravitino.dto.requests.UserAddRequest;
import org.apache.gravitino.dto.responses.CatalogListResponse;
import org.apache.gravitino.dto.responses.CatalogResponse;
import org.apache.gravitino.dto.responses.DeleteResponse;
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.EntityListResponse;
import org.apache.gravitino.dto.responses.ErrorResponse;
import org.apache.gravitino.dto.responses.GroupListResponse;
import org.apache.gravitino.dto.responses.GroupResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.responses.OwnerResponse;
import org.apache.gravitino.dto.responses.RemoveResponse;
import org.apache.gravitino.dto.responses.RoleResponse;
import org.apache.gravitino.dto.responses.SetResponse;
import org.apache.gravitino.dto.responses.TagListResponse;
import org.apache.gravitino.dto.responses.TagResponse;
import org.apache.gravitino.dto.responses.UserListResponse;
import org.apache.gravitino.dto.responses.UserResponse;
import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
import org.apache.gravitino.exceptions.IllegalPrivilegeException;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchGroupException;
import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.exceptions.NoSuchRoleException;
import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.exceptions.NoSuchUserException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
import org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.exceptions.UserAlreadyExistsException;
import org.apache.gravitino.rest.RESTUtils;
import org.apache.gravitino.tag.Tag;
import org.apache.gravitino.tag.TagChange;
import org.apache.gravitino.tag.TagOperations;

/**
 * Apache Gravitino Metalake is the top-level metadata repository for users. It contains a list of
 * catalogs as sub-level metadata collections. With {@link GravitinoMetalake}, users can list,
 * create, load, alter and drop a catalog with specified identifier.
 */
public class GravitinoMetalake extends MetalakeDTO
    implements SupportsCatalogs, TagOperations, SupportsRoles {
  private static final String API_METALAKES_CATALOGS_PATH = "api/metalakes/%s/catalogs/%s";
  private static final String API_PERMISSION_PATH = "api/metalakes/%s/permissions/%s";
  private static final String API_METALAKES_USERS_PATH = "api/metalakes/%s/users/%s";
  private static final String API_METALAKES_GROUPS_PATH = "api/metalakes/%s/groups/%s";
  private static final String API_METALAKES_ROLES_PATH = "api/metalakes/%s/roles/%s";
  private static final String API_METALAKES_OWNERS_PATH = "api/metalakes/%s/owners/%s";

  private static final String API_METALAKES_TAGS_PATH = "api/metalakes/%s/tags";
  private static final String BLANK_PLACEHOLDER = "";

  private final RESTClient restClient;
  private final MetadataObjectRoleOperations metadataObjectRoleOperations;

  GravitinoMetalake(
      String name,
      String comment,
      Map properties,
      AuditDTO auditDTO,
      RESTClient restClient) {
    super(name, comment, properties, auditDTO);
    this.restClient = restClient;
    this.metadataObjectRoleOperations =
        new MetadataObjectRoleOperations(
            name, MetadataObjects.of(null, name, MetadataObject.Type.METALAKE), restClient);
  }

  /**
   * List all the catalogs under this metalake.
   *
   * @return A list of the catalog names under the current metalake.
   * @throws NoSuchMetalakeException If the metalake does not exist.
   */
  @Override
  public String[] listCatalogs() throws NoSuchMetalakeException {

    EntityListResponse resp =
        restClient.get(
            String.format("api/metalakes/%s/catalogs", this.name()),
            EntityListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());
    resp.validate();

    return Arrays.stream(resp.identifiers()).map(NameIdentifier::name).toArray(String[]::new);
  }

  /**
   * List all the catalogs with their information under this metalake.
   *
   * @return A list of {@link Catalog} under the specified namespace.
   * @throws NoSuchMetalakeException if the metalake with specified namespace does not exist.
   */
  @Override
  public Catalog[] listCatalogsInfo() throws NoSuchMetalakeException {

    Map params = new HashMap<>();
    params.put("details", "true");
    CatalogListResponse resp =
        restClient.get(
            String.format("api/metalakes/%s/catalogs", this.name()),
            params,
            CatalogListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());

    return Arrays.stream(resp.getCatalogs())
        .map(c -> DTOConverters.toCatalog(this.name(), c, restClient))
        .toArray(Catalog[]::new);
  }

  /**
   * Load the catalog with specified identifier.
   *
   * @param catalogName The identifier of the catalog to load.
   * @return The {@link Catalog} with specified identifier.
   * @throws NoSuchCatalogException if the catalog with specified identifier does not exist.
   */
  @Override
  public Catalog loadCatalog(String catalogName) throws NoSuchCatalogException {

    CatalogResponse resp =
        restClient.get(
            String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName),
            CatalogResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());
    resp.validate();

    return DTOConverters.toCatalog(this.name(), resp.getCatalog(), restClient);
  }

  /**
   * Create a new catalog with specified identifier, type, comment and properties.
   *
   * @param catalogName The identifier of the catalog.
   * @param type The type of the catalog.
   * @param provider The provider of the catalog.
   * @param comment The comment of the catalog.
   * @param properties The properties of the catalog.
   * @return The created {@link Catalog}.
   * @throws NoSuchMetalakeException if the metalake with specified namespace does not exist.
   * @throws CatalogAlreadyExistsException if the catalog with specified identifier already exists.
   */
  @Override
  public Catalog createCatalog(
      String catalogName,
      Catalog.Type type,
      String provider,
      String comment,
      Map properties)
      throws NoSuchMetalakeException, CatalogAlreadyExistsException {
    CatalogCreateRequest req =
        new CatalogCreateRequest(catalogName, type, provider, comment, properties);
    req.validate();

    CatalogResponse resp =
        restClient.post(
            String.format("api/metalakes/%s/catalogs", this.name()),
            req,
            CatalogResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());
    resp.validate();

    return DTOConverters.toCatalog(this.name(), resp.getCatalog(), restClient);
  }

  /**
   * Alter the catalog with specified identifier by applying the changes.
   *
   * @param catalogName the identifier of the catalog.
   * @param changes the changes to apply to the catalog.
   * @return the altered {@link Catalog}.
   * @throws NoSuchCatalogException if the catalog with specified identifier does not exist.
   * @throws IllegalArgumentException if the changes are invalid.
   */
  @Override
  public Catalog alterCatalog(String catalogName, CatalogChange... changes)
      throws NoSuchCatalogException, IllegalArgumentException {
    List reqs =
        Arrays.stream(changes)
            .map(DTOConverters::toCatalogUpdateRequest)
            .collect(Collectors.toList());
    CatalogUpdatesRequest updatesRequest = new CatalogUpdatesRequest(reqs);
    updatesRequest.validate();

    CatalogResponse resp =
        restClient.put(
            String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName),
            updatesRequest,
            CatalogResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());
    resp.validate();

    return DTOConverters.toCatalog(this.name(), resp.getCatalog(), restClient);
  }

  /**
   * Drop the catalog with specified identifier.
   *
   * @param catalogName the name of the catalog.
   * @return true if the catalog is dropped successfully, false if the catalog does not exist.
   */
  @Override
  public boolean dropCatalog(String catalogName) {
    DropResponse resp =
        restClient.delete(
            String.format(API_METALAKES_CATALOGS_PATH, this.name(), catalogName),
            DropResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());
    resp.validate();
    return resp.dropped();
  }

  /**
   * Test whether a catalog can be created successfully with the specified parameters, without
   * actually creating it.
   *
   * @param catalogName the name of the catalog.
   * @param type the type of the catalog.
   * @param provider the provider of the catalog.
   * @param comment the comment of the catalog.
   * @param properties the properties of the catalog.
   * @throws Exception if the test failed.
   */
  @Override
  public void testConnection(
      String catalogName,
      Catalog.Type type,
      String provider,
      String comment,
      Map properties)
      throws Exception {
    CatalogCreateRequest req =
        new CatalogCreateRequest(catalogName, type, provider, comment, properties);
    req.validate();

    // The response maybe a `BaseResponse` (test successfully)  or an `ErrorResponse` (test failed),
    // we use the `ErrorResponse` here because it contains all fields of `BaseResponse` (code field
    // only)
    ErrorResponse resp =
        restClient.post(
            String.format("api/metalakes/%s/catalogs/testConnection", this.name()),
            req,
            ErrorResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.catalogErrorHandler());

    if (resp.getCode() == 0) {
      return;
    }

    // Throw the corresponding exception
    ErrorHandlers.catalogErrorHandler().accept(resp);
  }

  @Override
  public SupportsRoles supportsRoles() {
    return this;
  }

  /*
   * List all the tag names under a metalake.
   *
   * @return A list of the tag names under the current metalake.
   * @throws NoSuchMetalakeException If the metalake does not exist.
   */
  @Override
  public String[] listTags() throws NoSuchMetalakeException {
    NameListResponse resp =
        restClient.get(
            String.format(API_METALAKES_TAGS_PATH, this.name()),
            NameListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();
    return resp.getNames();
  }

  /**
   * List all the tags with detailed information under the current metalake.
   *
   * @return A list of {@link Tag} under the current metalake.
   * @throws NoSuchMetalakeException If the metalake does not exist.
   */
  @Override
  public Tag[] listTagsInfo() throws NoSuchMetalakeException {
    Map params = ImmutableMap.of("details", "true");
    TagListResponse resp =
        restClient.get(
            String.format(API_METALAKES_TAGS_PATH, this.name()),
            params,
            TagListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();

    return Arrays.stream(resp.getTags())
        .map(t -> new GenericTag(t, restClient, this.name()))
        .toArray(Tag[]::new);
  }

  /**
   * Get a tag by its name under the current metalake.
   *
   * @param name The name of the tag.
   * @return The tag.
   * @throws NoSuchTagException If the tag does not exist.
   */
  @Override
  public Tag getTag(String name) throws NoSuchTagException {
    Preconditions.checkArgument(StringUtils.isNotBlank(name), "tag name must not be null or empty");

    TagResponse resp =
        restClient.get(
            String.format(API_METALAKES_TAGS_PATH, this.name()) + "/" + name,
            TagResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();

    return new GenericTag(resp.getTag(), restClient, this.name());
  }

  /**
   * Create a tag under the current metalake.
   *
   * @param name The name of the tag.
   * @param comment The comment of the tag.
   * @param properties The properties of the tag.
   * @return The created tag.
   * @throws TagAlreadyExistsException If the tag already exists.
   */
  @Override
  public Tag createTag(String name, String comment, Map properties)
      throws TagAlreadyExistsException {
    Preconditions.checkArgument(StringUtils.isNotBlank(name), "tag name must not be null or empty");
    TagCreateRequest req = new TagCreateRequest(name, comment, properties);
    req.validate();

    TagResponse resp =
        restClient.post(
            String.format(API_METALAKES_TAGS_PATH, this.name()),
            req,
            TagResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();

    return new GenericTag(resp.getTag(), restClient, this.name());
  }

  /**
   * Alter a tag under the current metalake.
   *
   * @param name The name of the tag.
   * @param changes The changes to apply to the tag.
   * @return The altered tag.
   * @throws NoSuchTagException If the tag does not exist.
   * @throws IllegalArgumentException If the changes cannot be applied to the tag.
   */
  @Override
  public Tag alterTag(String name, TagChange... changes)
      throws NoSuchTagException, IllegalArgumentException {
    Preconditions.checkArgument(StringUtils.isNotBlank(name), "tag name must not be null or empty");
    List updates =
        Arrays.stream(changes).map(DTOConverters::toTagUpdateRequest).collect(Collectors.toList());
    TagUpdatesRequest req = new TagUpdatesRequest(updates);
    req.validate();

    TagResponse resp =
        restClient.put(
            String.format(API_METALAKES_TAGS_PATH, this.name()) + "/" + name,
            req,
            TagResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();

    return new GenericTag(resp.getTag(), restClient, this.name());
  }

  /**
   * Delete a tag under the current metalake.
   *
   * @param name The name of the tag.
   * @return True if the tag is deleted, false if the tag does not exist.
   */
  @Override
  public boolean deleteTag(String name) {
    Preconditions.checkArgument(StringUtils.isNotBlank(name), "tag name must not be null or empty");

    DropResponse resp =
        restClient.delete(
            String.format(API_METALAKES_TAGS_PATH, this.name()) + "/" + name,
            DropResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.tagErrorHandler());
    resp.validate();
    return resp.dropped();
  }

  /**
   * Adds a new User.
   *
   * @param user The name of the User.
   * @return The added User instance.
   * @throws UserAlreadyExistsException If a User with the same name already exists.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If adding the User encounters storage issues.
   */
  public User addUser(String user) throws UserAlreadyExistsException, NoSuchMetalakeException {
    UserAddRequest req = new UserAddRequest(user);
    req.validate();

    UserResponse resp =
        restClient.post(
            String.format(API_METALAKES_USERS_PATH, this.name(), BLANK_PLACEHOLDER),
            req,
            UserResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.userErrorHandler());
    resp.validate();

    return resp.getUser();
  }

  /**
   * Removes a User.
   *
   * @param user The name of the User.
   * @return True if the User was successfully removed, false only when there's no such user,
   *     otherwise it will throw an exception.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If removing the User encounters storage issues.
   */
  public boolean removeUser(String user) throws NoSuchMetalakeException {
    RemoveResponse resp =
        restClient.delete(
            String.format(API_METALAKES_USERS_PATH, this.name(), RESTUtils.encodeString(user)),
            RemoveResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.userErrorHandler());
    resp.validate();

    return resp.removed();
  }

  /**
   * Gets a User.
   *
   * @param user The name of the User.
   * @return The getting User instance.
   * @throws NoSuchUserException If the User with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If getting the User encounters storage issues.
   */
  public User getUser(String user) throws NoSuchUserException, NoSuchMetalakeException {
    UserResponse resp =
        restClient.get(
            String.format(API_METALAKES_USERS_PATH, this.name(), RESTUtils.encodeString(user)),
            UserResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.userErrorHandler());
    resp.validate();

    return resp.getUser();
  }

  /**
   * Lists the users.
   *
   * @return The User list.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   */
  public User[] listUsers() throws NoSuchMetalakeException {
    Map params = new HashMap<>();
    params.put("details", "true");

    UserListResponse resp =
        restClient.get(
            String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACEHOLDER),
            params,
            UserListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.userErrorHandler());
    resp.validate();

    return resp.getUsers();
  }

  /**
   * Lists the usernames.
   *
   * @return The username list.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   */
  public String[] listUserNames() throws NoSuchMetalakeException {
    NameListResponse resp =
        restClient.get(
            String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACEHOLDER),
            NameListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.userErrorHandler());
    resp.validate();

    return resp.getNames();
  }

  /**
   * Adds a new Group.
   *
   * @param group The name of the Group.
   * @return The Added Group instance.
   * @throws GroupAlreadyExistsException If a Group with the same name already exists.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If adding the Group encounters storage issues.
   */
  public Group addGroup(String group) throws GroupAlreadyExistsException, NoSuchMetalakeException {
    GroupAddRequest req = new GroupAddRequest(group);
    req.validate();

    GroupResponse resp =
        restClient.post(
            String.format(API_METALAKES_GROUPS_PATH, this.name(), BLANK_PLACEHOLDER),
            req,
            GroupResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.groupErrorHandler());
    resp.validate();

    return resp.getGroup();
  }

  /**
   * Removes a Group.
   *
   * @param group THe name of the Group.
   * @return True if the Group was successfully removed, false only when there's no such group,
   *     otherwise it will throw an exception.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If removing the Group encounters storage issues.
   */
  public boolean removeGroup(String group) throws NoSuchMetalakeException {
    RemoveResponse resp =
        restClient.delete(
            String.format(API_METALAKES_GROUPS_PATH, this.name(), RESTUtils.encodeString(group)),
            RemoveResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.groupErrorHandler());
    resp.validate();

    return resp.removed();
  }

  /**
   * Gets a Group.
   *
   * @param group The name of the Group.
   * @return The getting Group instance.
   * @throws NoSuchGroupException If the Group with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If getting the Group encounters storage issues.
   */
  public Group getGroup(String group) throws NoSuchGroupException, NoSuchMetalakeException {
    GroupResponse resp =
        restClient.get(
            String.format(API_METALAKES_GROUPS_PATH, this.name(), RESTUtils.encodeString(group)),
            GroupResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.groupErrorHandler());
    resp.validate();

    return resp.getGroup();
  }

  /**
   * Lists the groups
   *
   * @return The Group list
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   */
  public Group[] listGroups() throws NoSuchMetalakeException {
    Map params = new HashMap<>();
    params.put("details", "true");

    GroupListResponse resp =
        restClient.get(
            String.format(API_METALAKES_GROUPS_PATH, name(), BLANK_PLACEHOLDER),
            params,
            GroupListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.groupErrorHandler());
    resp.validate();
    return resp.getGroups();
  }

  /**
   * Lists the group names
   *
   * @return The Group Name List
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   */
  public String[] listGroupNames() throws NoSuchMetalakeException {
    NameListResponse resp =
        restClient.get(
            String.format(API_METALAKES_GROUPS_PATH, name(), BLANK_PLACEHOLDER),
            NameListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.groupErrorHandler());
    resp.validate();
    return resp.getNames();
  }

  /**
   * Gets a Role.
   *
   * @param role The name of the Role.
   * @return The getting Role instance.
   * @throws NoSuchRoleException If the Role with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If getting the Role encounters storage issues.
   */
  public Role getRole(String role) throws NoSuchRoleException, NoSuchMetalakeException {
    RoleResponse resp =
        restClient.get(
            String.format(API_METALAKES_ROLES_PATH, this.name(), RESTUtils.encodeString(role)),
            RoleResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.roleErrorHandler());
    resp.validate();

    return resp.getRole();
  }

  /**
   * Deletes a Role.
   *
   * @param role The name of the Role.
   * @return True if the Role was successfully deleted, false only when there's no such role,
   *     otherwise it will throw an exception.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If deleting the Role encounters storage issues.
   */
  public boolean deleteRole(String role) throws NoSuchMetalakeException {
    DeleteResponse resp =
        restClient.delete(
            String.format(API_METALAKES_ROLES_PATH, this.name(), RESTUtils.encodeString(role)),
            DeleteResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.roleErrorHandler());
    resp.validate();

    return resp.deleted();
  }

  /**
   * Creates a new Role.
   *
   * @param role The name of the Role.
   * @param properties The properties of the Role.
   * @param securableObjects The securable objects of the Role.
   * @return The created Role instance.
   * @throws RoleAlreadyExistsException If a Role with the same name already exists.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws NoSuchMetadataObjectException If the securable object doesn't exist
   * @throws RuntimeException If creating the Role encounters storage issues.
   */
  public Role createRole(
      String role, Map properties, List securableObjects)
      throws RoleAlreadyExistsException, NoSuchMetalakeException, NoSuchMetadataObjectException {
    RoleCreateRequest req =
        new RoleCreateRequest(
            role,
            properties,
            securableObjects.stream()
                .map(DTOConverters::toSecurableObject)
                .toArray(SecurableObjectDTO[]::new));
    req.validate();

    RoleResponse resp =
        restClient.post(
            String.format(API_METALAKES_ROLES_PATH, this.name(), BLANK_PLACEHOLDER),
            req,
            RoleResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.roleErrorHandler());
    resp.validate();

    return resp.getRole();
  }

  /**
   * Lists the role names.
   *
   * @return The role name list.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   */
  public String[] listRoleNames() {
    NameListResponse resp =
        restClient.get(
            String.format(API_METALAKES_ROLES_PATH, this.name(), BLANK_PLACEHOLDER),
            NameListResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.roleErrorHandler());
    resp.validate();

    return resp.getNames();
  }

  /**
   * Grant roles to a user.
   *
   * @param user The name of the User.
   * @param roles The names of the Role.
   * @return The Group after granted.
   * @throws NoSuchUserException If the User with the given name does not exist.
   * @throws NoSuchRoleException If the Role with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If granting roles to a user encounters storage issues.
   */
  public User grantRolesToUser(List roles, String user)
      throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException {
    RoleGrantRequest request = new RoleGrantRequest(roles);
    request.validate();

    UserResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format("users/%s/grant", RESTUtils.encodeString(user))),
            request,
            UserResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());
    resp.validate();

    return resp.getUser();
  }

  /**
   * Grant roles to a group.
   *
   * @param group The name of the Group.
   * @param roles The names of the Role.
   * @return The Group after granted.
   * @throws NoSuchGroupException If the Group with the given name does not exist.
   * @throws NoSuchRoleException If the Role with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If granting roles to a group encounters storage issues.
   */
  public Group grantRolesToGroup(List roles, String group)
      throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException {
    RoleGrantRequest request = new RoleGrantRequest(roles);
    request.validate();

    GroupResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format("groups/%s/grant", RESTUtils.encodeString(group))),
            request,
            GroupResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());
    resp.validate();

    return resp.getGroup();
  }

  /**
   * Revoke roles from a user.
   *
   * @param user The name of the User.
   * @param roles The names of the Role.
   * @return The User after revoked.
   * @throws NoSuchUserException If the User with the given name does not exist.
   * @throws NoSuchRoleException If the Role with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If revoking roles from a user encounters storage issues.
   */
  public User revokeRolesFromUser(List roles, String user)
      throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException {
    RoleRevokeRequest request = new RoleRevokeRequest(roles);
    request.validate();

    UserResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format("users/%s/revoke", RESTUtils.encodeString(user))),
            request,
            UserResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());
    resp.validate();

    return resp.getUser();
  }

  /**
   * Revoke roles from a group.
   *
   * @param group The name of the Group.
   * @param roles The names of the Role.
   * @return The Group after revoked.
   * @throws NoSuchGroupException If the Group with the given name does not exist.
   * @throws NoSuchRoleException If the Role with the given name does not exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws RuntimeException If revoking roles from a group encounters storage issues.
   */
  public Group revokeRolesFromGroup(List roles, String group)
      throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException {
    RoleRevokeRequest request = new RoleRevokeRequest(roles);
    request.validate();

    GroupResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format("groups/%s/revoke", RESTUtils.encodeString(group))),
            request,
            GroupResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());
    resp.validate();

    return resp.getGroup();
  }

  /**
   * Grant privileges to a role.
   *
   * @param role The name of the role.
   * @param privileges The privileges to grant.
   * @param object The object is associated with privileges to grant.
   * @return The role after granted.
   * @throws NoSuchRoleException If the role with the given name does not exist.
   * @throws NoSuchMetadataObjectException If the metadata object with the given name does not
   *     exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws IllegalPrivilegeException If any privilege can't be bind to the metadata object.
   * @throws RuntimeException If granting privileges to a role encounters storage issues.
   */
  public Role grantPrivilegesToRole(String role, MetadataObject object, List privileges)
      throws NoSuchRoleException, NoSuchMetadataObjectException, NoSuchMetalakeException,
          IllegalPrivilegeException {
    PrivilegeGrantRequest request =
        new PrivilegeGrantRequest(DTOConverters.toPrivileges(privileges));
    request.validate();

    RoleResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format(
                    "roles/%s/%s/%s/grant",
                    RESTUtils.encodeString(role),
                    object.type().name().toLowerCase(Locale.ROOT),
                    object.fullName())),
            request,
            RoleResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());

    resp.validate();

    return resp.getRole();
  }

  /**
   * Revoke privileges from a role.
   *
   * @param role The name of the role.
   * @param privileges The privileges to revoke.
   * @param object The object is associated with privileges to revoke.
   * @return The role after revoked.
   * @throws NoSuchRoleException If the role with the given name does not exist.
   * @throws NoSuchMetadataObjectException If the metadata object with the given name does not
   *     exist.
   * @throws NoSuchMetalakeException If the Metalake with the given name does not exist.
   * @throws IllegalPrivilegeException If any privilege can't be bind to the metadata object.
   * @throws RuntimeException If revoking privileges from a role encounters storage issues.
   */
  public Role revokePrivilegesFromRole(
      String role, MetadataObject object, List privileges)
      throws NoSuchRoleException, NoSuchMetadataObjectException, NoSuchMetalakeException,
          IllegalPrivilegeException {
    PrivilegeRevokeRequest request =
        new PrivilegeRevokeRequest(DTOConverters.toPrivileges(privileges));
    request.validate();

    RoleResponse resp =
        restClient.put(
            String.format(
                API_PERMISSION_PATH,
                this.name(),
                String.format(
                    "roles/%s/%s/%s/revoke",
                    RESTUtils.encodeString(role),
                    object.type().name().toLowerCase(Locale.ROOT),
                    object.fullName())),
            request,
            RoleResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.permissionOperationErrorHandler());

    resp.validate();

    return resp.getRole();
  }

  /**
   * Get the owner of a metadata object.
   *
   * @param object The metadata object
   * @return The owner of the metadata object. If the metadata object doesn't set the owner, it will
   *     return Optional.empty().
   * @throws NoSuchMetadataObjectException If the metadata object is not found.
   */
  public Optional getOwner(MetadataObject object) throws NoSuchMetadataObjectException {
    OwnerResponse resp =
        restClient.get(
            String.format(
                API_METALAKES_OWNERS_PATH,
                this.name(),
                String.format(
                    "%s/%s",
                    object.type().name().toLowerCase(Locale.ROOT),
                    RESTUtils.encodeString(object.fullName()))),
            OwnerResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.ownerErrorHandler());
    resp.validate();
    return Optional.ofNullable(resp.getOwner());
  }

  /**
   * Set the owner of a metadata object.
   *
   * @param object The metadata object.
   * @param ownerName The name of the owner
   * @param ownerType The type of the owner, The owner can be a user or a group.
   * @throws NotFoundException If the metadata object isn't found or the owner doesn't exist.
   */
  public void setOwner(MetadataObject object, String ownerName, Owner.Type ownerType)
      throws NotFoundException {
    OwnerSetRequest request = new OwnerSetRequest(ownerName, ownerType);
    request.validate();
    SetResponse resp =
        restClient.put(
            String.format(
                API_METALAKES_OWNERS_PATH,
                this.name(),
                String.format(
                    "%s/%s",
                    object.type().name().toLowerCase(Locale.ROOT),
                    RESTUtils.encodeString(object.fullName()))),
            request,
            SetResponse.class,
            Collections.emptyMap(),
            ErrorHandlers.ownerErrorHandler());
    resp.validate();
  }

  @Override
  public String[] listBindingRoleNames() {
    return metadataObjectRoleOperations.listBindingRoleNames();
  }

  static class Builder extends MetalakeDTO.Builder {
    private RESTClient restClient;

    private Builder() {
      super();
    }

    Builder withRestClient(RESTClient restClient) {
      this.restClient = restClient;
      return this;
    }

    @Override
    public GravitinoMetalake build() {
      Preconditions.checkNotNull(restClient, "restClient must be set");
      Preconditions.checkArgument(StringUtils.isNotBlank(name), "name must not be null or empty");
      Preconditions.checkArgument(audit != null, "audit must not be null");

      return new GravitinoMetalake(name, comment, properties, audit, restClient);
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }

    if (!(o instanceof GravitinoMetalake)) {
      return false;
    }

    GravitinoMetalake that = (GravitinoMetalake) o;
    return super.equals(that);
  }

  /** @return the builder for creating a new instance of GravitinoMetaLake. */
  public static Builder builder() {
    return new Builder();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy