org.kiji.schema.impl.cassandra.CassandraKijiResult.java Source code

Java tutorial

Introduction

Here is the source code for org.kiji.schema.impl.cassandra.CassandraKijiResult.java

Source

/**
 * (c) Copyright 2014 WibiData, Inc.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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.kiji.schema.impl.cassandra;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.ExecutionException;

import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.DriverException;
import com.datastax.driver.core.exceptions.DriverInternalError;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.kiji.annotations.ApiAudience;
import org.kiji.schema.EntityId;
import org.kiji.schema.KijiCell;
import org.kiji.schema.KijiColumnName;
import org.kiji.schema.KijiDataRequest;
import org.kiji.schema.KijiDataRequest.Column;
import org.kiji.schema.KijiDataRequestBuilder;
import org.kiji.schema.KijiResult;
import org.kiji.schema.KijiURI;
import org.kiji.schema.NoSuchColumnException;
import org.kiji.schema.cassandra.CassandraColumnName;
import org.kiji.schema.cassandra.CassandraTableName;
import org.kiji.schema.impl.DefaultKijiResult;
import org.kiji.schema.impl.EmptyKijiResult;
import org.kiji.schema.impl.MaterializedKijiResult;
import org.kiji.schema.layout.CassandraColumnNameTranslator;
import org.kiji.schema.layout.KijiTableLayout;
import org.kiji.schema.layout.impl.CellDecoderProvider;
import org.kiji.schema.layout.impl.ColumnId;

/**
 * A utility class which can create a {@link KijiResult} view on a Cassandra Kiji table.
 */
@ApiAudience.Private
public final class CassandraKijiResult {
    private static final Logger LOG = LoggerFactory.getLogger(CassandraKijiResult.class);

    /**
     * Create a new {@link KijiResult} backed by Cassandra.
     *
     * @param entityId EntityId of the row from which to read cells.
     * @param dataRequest KijiDataRequest defining the values to retrieve.
     * @param table The table being viewed.
     * @param layout The layout of the table.
     * @param columnTranslator A column name translator for the table.
     * @param decoderProvider A cell decoder provider for the table.
     * @param <T> The type of value in the {@code KijiCell} of this view.
     * @return an {@code KijiResult}.
     * @throws IOException On error while decoding cells.
     */
    public static <T> KijiResult<T> create(final EntityId entityId, final KijiDataRequest dataRequest,
            final CassandraKijiTable table, final KijiTableLayout layout,
            final CassandraColumnNameTranslator columnTranslator, final CellDecoderProvider decoderProvider)
            throws IOException {
        final KijiDataRequestBuilder unpagedRequestBuilder = KijiDataRequest.builder();
        final KijiDataRequestBuilder pagedRequestBuilder = KijiDataRequest.builder();
        unpagedRequestBuilder.withTimeRange(dataRequest.getMinTimestamp(), dataRequest.getMaxTimestamp());
        pagedRequestBuilder.withTimeRange(dataRequest.getMinTimestamp(), dataRequest.getMaxTimestamp());

        for (final Column columnRequest : dataRequest.getColumns()) {
            if (columnRequest.getFilter() != null) {
                throw new UnsupportedOperationException(String
                        .format("Cassandra Kiji does not support filters on column requests: %s.", columnRequest));
            }
            if (columnRequest.isPagingEnabled()) {
                pagedRequestBuilder.newColumnsDef(columnRequest);
            } else {
                unpagedRequestBuilder.newColumnsDef(columnRequest);
            }
        }

        final CellDecoderProvider requestDecoderProvider = decoderProvider
                .getDecoderProviderForRequest(dataRequest);

        final KijiDataRequest unpagedRequest = unpagedRequestBuilder.build();
        final KijiDataRequest pagedRequest = pagedRequestBuilder.build();

        if (unpagedRequest.isEmpty() && pagedRequest.isEmpty()) {
            return new EmptyKijiResult<T>(entityId, dataRequest);
        }

        final MaterializedKijiResult<T> materializedKijiResult;
        if (!unpagedRequest.isEmpty()) {
            materializedKijiResult = createMaterialized(table.getURI(), entityId, unpagedRequest, layout,
                    columnTranslator, requestDecoderProvider, table.getAdmin());
        } else {
            materializedKijiResult = null;
        }

        final CassandraPagedKijiResult<T> pagedKijiResult;
        if (!pagedRequest.isEmpty()) {
            pagedKijiResult = new CassandraPagedKijiResult<T>(entityId, pagedRequest, table, layout,
                    columnTranslator, requestDecoderProvider);
        } else {
            pagedKijiResult = null;
        }

        if (unpagedRequest.isEmpty()) {
            return pagedKijiResult;
        } else if (pagedRequest.isEmpty()) {
            return materializedKijiResult;
        } else {
            return DefaultKijiResult.create(dataRequest, materializedKijiResult, pagedKijiResult);
        }
    }

    /**
     * Create a materialized {@code KijiResult} for a get on a Cassandra Kiji table.
     *
     * @param tableURI The table URI.
     * @param entityId The entity ID of the row to get.
     * @param dataRequest The data request defining the columns to get. All columns must be non-paged.
     * @param layout The layout of the table.
     * @param translator A column name translator for the table.
     * @param decoderProvider A decoder provider for the table.
     * @param admin The Cassandra connection.
     * @param <T> The value type of cells in the result.
     * @return A materialized {@code KijiResult} for the row.
     */
    public static <T> MaterializedKijiResult<T> createMaterialized(final KijiURI tableURI, final EntityId entityId,
            final KijiDataRequest dataRequest, final KijiTableLayout layout,
            final CassandraColumnNameTranslator translator, final CellDecoderProvider decoderProvider,
            final CassandraAdmin admin) {

        SortedMap<KijiColumnName, ListenableFuture<Iterator<KijiCell<T>>>> resultFutures = Maps.newTreeMap();

        for (final Column columnRequest : dataRequest.getColumns()) {
            Preconditions.checkArgument(!columnRequest.isPagingEnabled(),
                    "CassandraMaterializedKijiResult can not be created with a paged data request: %s.",
                    dataRequest);

            resultFutures.put(columnRequest.getColumnName(), CassandraKijiResult.<T>getColumn(tableURI, entityId,
                    columnRequest, dataRequest, layout, translator, decoderProvider, admin));
        }

        SortedMap<KijiColumnName, List<KijiCell<T>>> results = Maps.newTreeMap();
        for (Map.Entry<KijiColumnName, ListenableFuture<Iterator<KijiCell<T>>>> entry : resultFutures.entrySet()) {

            results.put(entry.getKey(), Lists.newArrayList(CassandraKijiResult.unwrapFuture(entry.getValue())));
        }

        return MaterializedKijiResult.create(entityId, dataRequest, layout, results);
    }

    // CSOFF: ParameterNumber
    /**
     * Query Cassandra for a Kiji qualified-column or column-family in a Kiji row. The result is a
     * future containing an iterator over the result cells.
     *
     * @param tableURI The table URI.
     * @param entityId The entity ID of the row in the Kiji table.
     * @param columnRequest The requested column.
     * @param dataRequest The data request defining the request options.
     * @param layout The table's layout.
     * @param translator A column name translator for the table.
     * @param decoderProvider A decoder provider for the table.
     * @param admin The Cassandra connection to use for querying.
     * @param <T> The value type of the column.
     * @return A future containing an iterator of cells in the column.
     */
    public static <T> ListenableFuture<Iterator<KijiCell<T>>> getColumn(final KijiURI tableURI,
            final EntityId entityId, final Column columnRequest, final KijiDataRequest dataRequest,
            final KijiTableLayout layout, final CassandraColumnNameTranslator translator,
            final CellDecoderProvider decoderProvider, final CassandraAdmin admin) {
        final KijiColumnName column = columnRequest.getColumnName();
        final CassandraColumnName cassandraColumn;
        try {
            cassandraColumn = translator.toCassandraColumnName(column);
        } catch (NoSuchColumnException e) {
            throw new IllegalArgumentException(String.format("No such column '%s' in table %s.", column, tableURI));
        }

        final ColumnId localityGroupId = layout.getFamilyMap().get(column.getFamily()).getLocalityGroup().getId();
        final CassandraTableName table = CassandraTableName.getLocalityGroupTableName(tableURI, localityGroupId);

        if (column.isFullyQualified()) {

            final Statement statement = CQLUtils.getQualifiedColumnGetStatement(layout, table, entityId,
                    cassandraColumn, dataRequest, columnRequest);

            return Futures.transform(admin.executeAsync(statement),
                    RowDecoders.<T>getQualifiedColumnDecoderFunction(column, decoderProvider));
        } else {

            if (columnRequest.getMaxVersions() != 0) {
                LOG.warn(
                        "Cassandra Kiji can not efficiently get a column family with max versions"
                                + " (column family: {}, max version: {}). Filtering versions on the client.",
                        column, columnRequest.getMaxVersions());
            }

            if (dataRequest.getMaxTimestamp() != Long.MAX_VALUE
                    || dataRequest.getMinTimestamp() != Long.MIN_VALUE) {
                LOG.warn(
                        "Cassandra Kiji can not efficiently restrict a timestamp on a column family: "
                                + " (column family: {}, data request: {}). Filtering timestamps on the client.",
                        column, dataRequest);
            }

            final Statement statement = CQLUtils.getColumnFamilyGetStatement(layout, table, entityId,
                    cassandraColumn, columnRequest);

            return Futures.transform(admin.executeAsync(statement), RowDecoders.<T>getColumnFamilyDecoderFunction(
                    table, column, columnRequest, dataRequest, layout, translator, decoderProvider));
        }
    }
    // CSON: ParameterNumber

    /**
     * Unwrap a Cassandra listenable future.
     *
     * @param future The future to unwrap.
     * @param <T> The value type of the future.
     * @return The future's value.
     */
    public static <T> T unwrapFuture(final ListenableFuture<T> future) {
        // See DefaultResultSetFuture#getUninterruptibly
        try {
            return Uninterruptibles.getUninterruptibly(future);
        } catch (ExecutionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof DriverException) {
                throw (DriverException) cause;
            } else {
                throw new DriverInternalError("Unexpected exception thrown", cause);
            }
        }
    }

    /**
     * Constructor for non-instantiable helper class.
     */
    private CassandraKijiResult() {
    }
}