net.oneandone.troilus.ListReadQuery.java Source code

Java tutorial

Introduction

Here is the source code for net.oneandone.troilus.ListReadQuery.java

Source

/*
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 * 
 * 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 net.oneandone.troilus;

import static com.datastax.driver.core.querybuilder.QueryBuilder.select;

import java.util.List;
import java.util.concurrent.ExecutionException;

import net.oneandone.troilus.java7.FetchingIterator;
import net.oneandone.troilus.java7.ListRead;
import net.oneandone.troilus.java7.ListReadWithUnit;
import net.oneandone.troilus.java7.Record;
import net.oneandone.troilus.java7.ResultList;
import net.oneandone.troilus.java7.interceptor.ReadQueryData;
import net.oneandone.troilus.java7.interceptor.ReadQueryRequestInterceptor;
import net.oneandone.troilus.java7.interceptor.ReadQueryResponseInterceptor;

import org.reactivestreams.Publisher;

import com.datastax.driver.core.PagingState;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Clause;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * The list read query implementation
 * 
 * @author Jason Westra - edited original
 * 12-12-2015: 3.x API change - ListenableFuture<Void> to ListenableFuture<ResultSet>
 * 12-13-2015: pagination APIs - withPagingState(), added toStatementAsync() - to set fetchSize & pagingState properly
 *             modified executeAsync() - calls toStatementAsync() to set up pagination properties, if needed
 *
 */
class ListReadQuery extends AbstractQuery<ListReadQuery> implements ListReadWithUnit<ResultList<Record>, Record> {

    private final ReadQueryData data;

    /**
     * @param ctx   the context 
     * @param data  the data
     */
    ListReadQuery(Context ctx, ReadQueryData data) {
        super(ctx);
        this.data = data;
    }

    ////////////////////
    // factory methods

    @Override
    protected ListReadQuery newQuery(Context newContext) {
        return new ListReadQuery(newContext, data);
    }

    private ListReadQuery newQuery(ReadQueryData data) {
        return new ListReadQuery(getContext(), data);
    }

    //
    ////////////////////

    @Override
    public ListReadQuery all() {
        return newQuery(data.columnsToFetch(ImmutableMap.<String, Boolean>of()));
    }

    private ListReadQuery columns(ImmutableCollection<String> namesToRead) {
        ListReadQuery read = this;
        for (String columnName : namesToRead) {
            read = read.column(columnName);
        }
        return read;
    }

    @Override
    public ListReadQuery column(String name) {
        return newQuery(data.columnsToFetch(Immutables.join(data.getColumnsToFetch(), name, false)));
    }

    @Override
    public ListReadQuery columnWithMetadata(String name) {
        return newQuery(data.columnsToFetch(Immutables.join(data.getColumnsToFetch(), name, true)));
    }

    @Override
    public ListReadQuery columns(String... names) {
        return columns(ImmutableSet.copyOf(names));
    }

    @Override
    public ListReadQuery column(ColumnName<?> name) {
        return column(name.getName());
    }

    @Override
    public ListReadQuery columnWithMetadata(ColumnName<?> name) {
        return columnWithMetadata(name.getName());
    }

    @Override
    public ListReadQuery columns(ColumnName<?>... names) {
        List<String> ns = Lists.newArrayList();
        for (ColumnName<?> name : names) {
            ns.add(name.getName());
        }
        return columns(ImmutableList.copyOf(ns));
    }

    @Override
    public ListReadQuery withLimit(int limit) {
        return newQuery(data.limit(limit));
    }

    @Override
    public ListReadQuery withAllowFiltering() {
        return newQuery(data.allowFiltering(true));
    }

    @Override
    public ListReadQuery withFetchSize(int fetchSize) {
        return newQuery(data.fetchSize(fetchSize));
    }

    @Override
    public ListReadQuery withDistinct() {
        return newQuery(data.distinct(true));
    }

    @Override
    public ListReadQuery withPagingState(PagingState pagingState) {
        return newQuery(data.pagingState(pagingState));
    }

    @Override
    public CountReadQuery count() {
        return new CountReadQuery(getContext(),
                new CountReadQueryData(data.getTablename()).whereConditions(data.getWhereConditions())
                        .limit(data.getLimit()).fetchSize(data.getFetchSize())
                        .allowFiltering(data.getAllowFiltering()).distinct(data.getDistinct()));
    }

    @Override
    public <E> ListEntityReadQuery<E> asEntity(Class<E> objectClass) {
        return new ListEntityReadQuery<>(getContext(), this, objectClass);
    }

    @Override
    public Publisher<Record> executeRx() {
        ListenableFuture<ResultList<Record>> recordsFuture = executeAsync();
        return new ResultListPublisher<>(recordsFuture);
    }

    @Override
    public ResultList<Record> execute() {
        return ListenableFutures.getUninterruptibly(executeAsync());
    }

    @Override
    public ListenableFuture<ResultList<Record>> executeAsync() {
        // perform request executors
        ListenableFuture<ReadQueryData> queryDataFuture = executeRequestInterceptorsAsync(
                Futures.<ReadQueryData>immediateFuture(data));

        // execute query asnyc
        Function<ReadQueryData, ListenableFuture<ResultList<Record>>> queryExecutor = new Function<ReadQueryData, ListenableFuture<ResultList<Record>>>() {
            @Override
            public ListenableFuture<ResultList<Record>> apply(ReadQueryData querData) {
                return executeAsync(querData, getDefaultDbSession());
            }
        };
        return ListenableFutures.transform(queryDataFuture, queryExecutor);
    }

    private ListenableFuture<ResultList<Record>> executeAsync(final ReadQueryData queryData, DBSession dbSession) {
        // 12-11-2015: jwestra - pagination support. Calls toStatementAsync() to perform
        // extra post-processing on the Statement to do pagination correctly
        // perform query
        ListenableFuture<ResultSet> resultSetFuture = performAsync(dbSession,
                toStatementAsync(queryData, getUDTValueMapper(), dbSession));

        //ListenableFuture<ResultSet> resultSetFuture = performAsync(dbSession, ReadQueryDataImpl.toStatementAsync(queryData, getUDTValueMapper(), dbSession));        

        // result set to record list mapper
        Function<ResultSet, ResultList<Record>> resultSetToRecordList = new Function<ResultSet, ResultList<Record>>() {

            @Override
            public ResultList<Record> apply(ResultSet resultSet) {
                return new RecordListImpl(getContext(), queryData, resultSet);
            }
        };
        ListenableFuture<ResultList<Record>> recordListFuture = Futures.transform(resultSetFuture,
                resultSetToRecordList);

        // running interceptors within dedicated threads!
        return executeResponseInterceptorsAsync(queryData, recordListFuture);
    }

    private ListenableFuture<ReadQueryData> executeRequestInterceptorsAsync(
            ListenableFuture<ReadQueryData> queryDataFuture) {

        for (ReadQueryRequestInterceptor interceptor : getInterceptorRegistry()
                .getInterceptors(ReadQueryRequestInterceptor.class).reverse()) {
            final ReadQueryRequestInterceptor icptor = interceptor;

            Function<ReadQueryData, ListenableFuture<ReadQueryData>> mapperFunction = new Function<ReadQueryData, ListenableFuture<ReadQueryData>>() {
                @Override
                public ListenableFuture<ReadQueryData> apply(ReadQueryData queryData) {
                    return icptor.onReadRequestAsync(queryData);
                }
            };

            // running interceptors within dedicated threads!
            queryDataFuture = ListenableFutures.transform(queryDataFuture, mapperFunction, getExecutor());
        }

        return queryDataFuture;
    }

    private ListenableFuture<ResultList<Record>> executeResponseInterceptorsAsync(final ReadQueryData queryData,
            ListenableFuture<ResultList<Record>> recordFuture) {

        for (ReadQueryResponseInterceptor interceptor : getInterceptorRegistry()
                .getInterceptors(ReadQueryResponseInterceptor.class).reverse()) {
            final ReadQueryResponseInterceptor icptor = interceptor;

            Function<ResultList<Record>, ListenableFuture<ResultList<Record>>> mapperFunction = new Function<ResultList<Record>, ListenableFuture<ResultList<Record>>>() {
                @Override
                public ListenableFuture<ResultList<Record>> apply(ResultList<Record> recordList) {
                    return icptor.onReadResponseAsync(queryData, recordList);
                }
            };

            // running interceptors within dedicagted threads!
            recordFuture = ListenableFutures.transform(recordFuture, mapperFunction, getExecutor());
        }

        return recordFuture;
    }

    /**
     * 12-11-2015: jwestra
     * 
     * Prepares the Statement for Pagination, if fetchSize is set. Otherwise, it simply  
     * returns ReadQueryDataImpl.toStatementAsync(data, udtValueMapper, dbSession)
     * as in the original code did in executeAsync().
     * 
     * @param queryData
     * @param udtValueMapper
     * @param dbSession
     * 
     * @return ListenableFuture<Statement>
     */
    private ListenableFuture<Statement> toStatementAsync(final ReadQueryData queryData,
            UDTValueMapper udtValueMapper, DBSession dbSession) {
        ListenableFuture<Statement> lfs = ReadQueryDataImpl.toStatementAsync(data, udtValueMapper, dbSession);

        Integer fetchSize = data.getFetchSize();
        if (fetchSize != null) {
            Statement statement = null;
            try {
                statement = lfs.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException("Failed to get the Statement from ListenableFuture<Statement>", e);
            }

            // The FetchSize is lost somehow when ReadQueryData.toStatementAsync() is invoked.
            // In the debugger, it was always zero.  So, it is reset here directly on the Statement
            // This sets the fetchsize specifically on the Statement before executing.
            statement.setFetchSize(fetchSize);

            // The PagingState is not set during ReadQueryData.toStatementAsync() because
            // the driver compares the Select (a RegularStatement) to the previous PagingState's
            // BoundStatement and fails the hash() check with a PagingStateException.
            // So, like the fetch size, the PagingState must be done here.
            statement.setPagingState(data.getPagingState());
        }
        return lfs;
    }

    /**
     * The entity list read implementation
     * @param <E> the entity type
     */
    static class ListEntityReadQuery<E> extends AbstractQuery<ListEntityReadQuery<E>>
            implements ListRead<ResultList<E>, E> {
        private final ListReadQuery query;
        private final Class<E> clazz;

        /**
         * @param ctx   the context
         * @param query the query 
         * @param clazz the entity type
         */
        ListEntityReadQuery(Context ctx, ListReadQuery query, Class<E> clazz) {
            super(ctx);
            this.query = query;
            this.clazz = clazz;
        }

        @Override
        protected ListEntityReadQuery<E> newQuery(Context newContext) {
            return new ListReadQuery(newContext, query.data).asEntity(clazz);
        }

        @Override
        public ListEntityReadQuery<E> withDistinct() {
            return query.withDistinct().asEntity(clazz);
        }

        @Override
        public ListEntityReadQuery<E> withFetchSize(int fetchSize) {
            return query.withFetchSize(fetchSize).asEntity(clazz);
        }

        @Override
        public ListEntityReadQuery<E> withAllowFiltering() {
            return query.withAllowFiltering().asEntity(clazz);
        }

        @Override
        public ListEntityReadQuery<E> withLimit(int limit) {
            return query.withLimit(limit).asEntity(clazz);
        }

        @Override
        public ResultList<E> execute() {
            return ListenableFutures.getUninterruptibly(executeAsync());
        }

        @Override
        public ListenableFuture<ResultList<E>> executeAsync() {
            ListenableFuture<ResultList<Record>> future = query.executeAsync();

            Function<ResultList<Record>, ResultList<E>> mapEntity = new Function<ResultList<Record>, ResultList<E>>() {
                @Override
                public ResultList<E> apply(ResultList<Record> recordList) {
                    return new EntityListImpl<>(query.data.getTablename(), getBeanMapper(), getCatalog(),
                            recordList, clazz);
                }
            };

            return Futures.transform(future, mapEntity);
        }

        @Override
        public Publisher<E> executeRx() {
            ListenableFuture<ResultList<E>> recordsFuture = executeAsync();
            return new ResultListPublisher<>(recordsFuture);
        }

        @Override
        public ListEntityReadQuery<E> withPagingState(PagingState pagingState) {
            return query.withPagingState(pagingState).asEntity(clazz);
        }

    }

    private static class EntityListImpl<F> extends ResultAdapter implements ResultList<F> {
        private final Tablename tablename;
        private final BeanMapper beanMapper;
        private final MetadataCatalog catalog;
        private final ResultList<Record> recordList;
        private final Class<F> clazz;

        EntityListImpl(Tablename tablename, BeanMapper beanMapper, MetadataCatalog catalog,
                ResultList<Record> recordList, Class<F> clazz) {
            super(recordList);
            this.tablename = tablename;
            this.beanMapper = beanMapper;
            this.catalog = catalog;
            this.recordList = recordList;
            this.clazz = clazz;
        }

        public FetchingIterator<F> iterator() {

            return new FetchingIterator<F>() {
                private final FetchingIterator<Record> recordIt = recordList.iterator();

                @Override
                public boolean hasNext() {
                    return recordIt.hasNext();
                }

                @Override
                public F next() {
                    return beanMapper.fromValues(clazz, RecordImpl.toPropertiesSource(recordIt.next()),
                            catalog.getColumnNames(tablename));
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }

                @Override
                public int getAvailableWithoutFetching() {
                    return recordIt.getAvailableWithoutFetching();
                }

                @Override
                public boolean isFullyFetched() {
                    return recordIt.isFullyFetched();
                }

                @Override
                public ListenableFuture<ResultSet> fetchMoreResultsAsync() {
                    return recordIt.fetchMoreResultsAsync();
                }
            };
        }
    }

    private static final class CountReadQueryData {
        final Tablename tablename;
        final ImmutableSet<Clause> whereClauses;
        final Integer limit;
        final Boolean allowFiltering;
        final Integer fetchSize;
        final Boolean distinct;

        public CountReadQueryData(Tablename tablename) {
            this(tablename, ImmutableSet.<Clause>of(), null, null, null, null);
        }

        private CountReadQueryData(Tablename tablename, ImmutableSet<Clause> whereClauses, Integer limit,
                Boolean allowFiltering, Integer fetchSize, Boolean distinct) {
            this.tablename = tablename;
            this.whereClauses = whereClauses;
            this.limit = limit;
            this.allowFiltering = allowFiltering;
            this.fetchSize = fetchSize;
            this.distinct = distinct;
        }

        public CountReadQueryData whereConditions(ImmutableSet<Clause> whereClauses) {
            return new CountReadQueryData(this.tablename, whereClauses, this.limit, this.allowFiltering,
                    this.fetchSize, this.distinct);
        }

        public CountReadQueryData limit(Integer limit) {
            return new CountReadQueryData(this.tablename, this.whereClauses, limit, this.allowFiltering,
                    this.fetchSize, this.distinct);
        }

        public CountReadQueryData allowFiltering(Boolean allowFiltering) {
            return new CountReadQueryData(this.tablename, this.whereClauses, this.limit, allowFiltering,
                    this.fetchSize, this.distinct);
        }

        public CountReadQueryData fetchSize(Integer fetchSize) {
            return new CountReadQueryData(this.tablename, this.whereClauses, this.limit, this.allowFiltering,
                    fetchSize, this.distinct);
        }

        public CountReadQueryData distinct(Boolean distinct) {
            return new CountReadQueryData(this.tablename, this.whereClauses, this.limit, this.allowFiltering,
                    this.fetchSize, distinct);
        }

        public Tablename getTablename() {
            return tablename;
        }

        public ImmutableSet<Clause> getWhereConditions() {
            return whereClauses;
        }

        public Integer getLimit() {
            return limit;
        }

        public Boolean getAllowFiltering() {
            return allowFiltering;
        }

        public Integer getFetchSize() {
            return fetchSize;
        }

        public Boolean getDistinct() {
            return distinct;
        }
    }

    static class CountReadQuery extends AbstractQuery<CountReadQuery> implements ListRead<Count, Count> {

        private final CountReadQueryData data;

        public CountReadQuery(Context ctx, CountReadQueryData data) {
            super(ctx);
            this.data = data;
        }

        @Override
        protected CountReadQuery newQuery(Context newContext) {
            return new CountReadQuery(newContext, data);
        }

        @Override
        public CountReadQuery withLimit(int limit) {
            return new CountReadQuery(getContext(), data.limit(limit));
        }

        @Override
        public CountReadQuery withAllowFiltering() {
            return new CountReadQuery(getContext(), data.allowFiltering(true));
        }

        @Override
        public CountReadQuery withFetchSize(int fetchSize) {
            return new CountReadQuery(getContext(), data.fetchSize(fetchSize));
        }

        @Override
        public CountReadQuery withDistinct() {
            return new CountReadQuery(getContext(), data.distinct(true));
        }

        private Statement toStatement(CountReadQueryData queryData) {
            Select.Selection selection = select();

            if (queryData.getDistinct() != null) {
                if (queryData.getDistinct()) {
                    selection.distinct();
                }
                ;
            }

            selection.countAll();

            Select select = selection.from(data.getTablename().getTablename());

            for (Clause whereCondition : queryData.getWhereConditions()) {
                select.where(whereCondition);
            }

            if (queryData.getLimit() != null) {
                select.limit(queryData.getLimit());
            }

            if (queryData.getAllowFiltering() != null) {
                if (queryData.getAllowFiltering()) {
                    select.allowFiltering();
                }
            }

            if (queryData.getFetchSize() != null) {
                select.setFetchSize(queryData.getFetchSize());
            }

            return select;
        }

        @Override
        public Count execute() {
            return ListenableFutures.getUninterruptibly(executeAsync());
        }

        @Override
        public ListenableFuture<Count> executeAsync() {
            ListenableFuture<ResultSet> future = performAsync(getDefaultDbSession(), toStatement(data));

            Function<ResultSet, Count> mapEntity = new Function<ResultSet, Count>() {
                @Override
                public Count apply(ResultSet resultSet) {
                    return Count.newCountResult(resultSet);
                }
            };

            return Futures.transform(future, mapEntity);
        }

        @Override
        public Publisher<Count> executeRx() {
            ListenableFuture<Count> countFuture = executeAsync();

            Function<Count, ResultList<Count>> toListFunction = new Function<Count, ResultList<Count>>() {
                @Override
                public ResultList<Count> apply(Count count) {
                    return new SingleEntryResultListAdapter<>(count);
                }
            };

            return new ResultListPublisher<>(Futures.transform(countFuture, toListFunction));
        }

        @Override
        public ListRead<Count, Count> withPagingState(PagingState pagingState) {
            throw new IllegalArgumentException("Count readers cannot be configured with paging state.");
        }
    }
}