package com.airbnb.airpal.resources;
import com.airbnb.airpal.core.AirpalUser;
import com.airbnb.airpal.core.store.usage.UsageStore;
import com.airbnb.airpal.presto.PartitionedTable;
import com.airbnb.airpal.presto.Table;
import com.airbnb.airpal.presto.hive.HivePartition;
import com.airbnb.airpal.presto.metadata.ColumnCache;
import com.airbnb.airpal.presto.metadata.PreviewTableCache;
import com.airbnb.airpal.presto.metadata.SchemaCache;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.dropwizard.util.Duration;
import lombok.Data;
import lombok.NonNull;
import org.joda.time.DateTime;
import org.secnod.shiro.jaxrs.Auth;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static com.airbnb.airpal.core.AuthorizationUtil.isAuthorizedRead;
import static com.airbnb.airpal.presto.hive.HivePartition.HivePartitionItem;
import static java.lang.String.format;
@Path("/api/table")
public class TablesResource
{
private final SchemaCache schemaCache;
private final ColumnCache columnCache;
private final PreviewTableCache previewTableCache;
private final UsageStore usageStore;
private final String defaultCatalog;
@Inject
public TablesResource(
final SchemaCache schemaCache,
final ColumnCache columnCache,
final PreviewTableCache previewTableCache,
final UsageStore usageStore,
@Named("default-catalog") final String defaultCatalog)
{
this.schemaCache = schemaCache;
this.columnCache = columnCache;
this.previewTableCache = previewTableCache;
this.usageStore = usageStore;
this.defaultCatalog = defaultCatalog;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getTableUpdates(
@Auth AirpalUser user,
@QueryParam("catalog") Optional<String> catalogOptional)
{
final String catalog = catalogOptional.or(defaultCatalog);
final Map<String, List<String>> schemaMap = schemaCache.getSchemaMap(catalog);
final ImmutableList.Builder<Table> builder = ImmutableList.builder();
for (Map.Entry<String, List<String>> entry : schemaMap.entrySet()) {
String schema = entry.getKey();
for (String table : entry.getValue()) {
if (isAuthorizedRead(user, catalog, schema, table)) {
builder.add(new Table(catalog, schema, table));
}
}
}
final List<Table> tables = builder.build();
final Map<Table, Long> allUsages = usageStore.getUsages(tables);
final Map<PartitionedTable, DateTime> updateMap = Collections.emptyMap();
return Response.ok(createTablesWithMetaData(tables, allUsages, updateMap)).build();
}
// TODO: Make getTableColumns, getTablePartitions and getTablePreview take a 3rd path parameter for catalog
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{schema}/{tableName}/columns")
public Response getTableColumns(
@Auth AirpalUser user,
@PathParam("schema") String schema,
@PathParam("tableName") String tableName)
throws ExecutionException
{
if (isAuthorizedRead(user, defaultCatalog, schema, tableName)) {
return Response.ok(columnCache.getColumns(schema, tableName)).build();
}
else {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{schema}/{tableName}/partitions")
public Response getTablePartitions(
@Auth AirpalUser user,
@PathParam("schema") String schema,
@PathParam("tableName") String tableName)
throws ExecutionException
{
if (isAuthorizedRead(user, defaultCatalog, schema, tableName)) {
return Response.ok(getPartitionsWithMetaData(new PartitionedTable("hive", schema, tableName))).build();
}
else {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{schema}/{tableName}/preview")
public Response getTablePreview(
@Auth AirpalUser user,
@PathParam("schema") String schema,
@PathParam("tableName") String tableName,
@QueryParam("connectorId") String connectorId,
@QueryParam("partitionName") final String partitionName,
@QueryParam("partitionValue") String partitionValue)
throws ExecutionException
{
List<HivePartition> partitions = columnCache.getPartitions(schema, tableName);
Optional<HivePartition> partition = FluentIterable.from(partitions).firstMatch(
new Predicate<HivePartition>()
{
@Override
public boolean apply(HivePartition input)
{
return Objects.equals(input.getName(), partitionName);
}
});
if (isAuthorizedRead(user, defaultCatalog, schema, tableName)) {
return Response.ok(previewTableCache.getPreview(
Optional.fromNullable(connectorId).or(defaultCatalog),
schema,
tableName,
partition,
partitionValue)).build();
}
else {
return Response.status(Response.Status.FORBIDDEN).build();
}
}
@Data
public static class PartitionedTableWithMetaData
{
@JsonProperty
private final String schema;
@JsonProperty
private final String tableName;
@JsonProperty
private final String partition;
@JsonProperty
private final String fqn;
@JsonProperty
private final long usages;
@JsonProperty
private final int windowCount;
@JsonProperty
private final TimeUnit windowUnit;
@JsonProperty
private final DateTime lastUpdated;
public static PartitionedTableWithMetaData fromTable(final Table table,
final long usages,
final TimeUnit windowUnit,
final int windowCount,
final DateTime lastUpdated)
{
return fromPartionedTable(PartitionedTable.fromTable(table),
usages,
windowUnit,
windowCount,
lastUpdated);
}
public static PartitionedTableWithMetaData fromPartionedTable(final PartitionedTable table,
final long usages,
final TimeUnit windowUnit,
final int windowCount,
final DateTime lastUpdated)
{
return new PartitionedTableWithMetaData(table.getSchema(),
table.getTable(),
table.getPartitionName(),
format("%s.%s", table.getSchema(), table.getTable()),
usages,
windowCount,
windowUnit,
lastUpdated);
}
}
private List<PartitionedTableWithMetaData> createTablesWithMetaData(
@NonNull final List<Table> tables,
@NonNull final Map<Table, Long> tableUsageMap,
@NonNull final Map<PartitionedTable, DateTime> tableUpdateMap)
{
final ImmutableList.Builder<PartitionedTableWithMetaData> builder = ImmutableList.builder();
final Duration usageWindow = usageStore.window();
for (Table table : tables) {
PartitionedTable partitionedTable = PartitionedTable.fromTable(table);
DateTime updatedAt = tableUpdateMap.get(partitionedTable);
long lastUsage = 0;
if (tableUsageMap.containsKey(table)) {
lastUsage = tableUsageMap.get(table);
}
builder.add(PartitionedTableWithMetaData.fromTable(
table,
lastUsage,
usageWindow.getUnit(),
(int) usageWindow.getQuantity(),
updatedAt
));
}
return builder.build();
}
private List<HivePartitionItem> getPartitionsWithMetaData(PartitionedTable table)
throws ExecutionException
{
List<HivePartition> partitions = columnCache.getPartitions(table.getSchema(), table.getTable());
ImmutableList.Builder<HivePartitionItem> partitionItems = ImmutableList.builder();
for (HivePartition partition : partitions) {
for (Object value : partition.getValues()) {
PartitionedTable partitionedTable = table.withPartitionName(
HivePartition.getPartitionId(partition.getName(), value));
DateTime updatedAt = null;
partitionItems.add(new HivePartitionItem(partition.getName(), partition.getType(), value, updatedAt));
}
}
return partitionItems.build();
}
}