/* * Copyright 2010-2014 Ning, Inc. * * Ning 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.killbill.billing.util.entity.dao; import java.util.LinkedList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.killbill.billing.BillingExceptionBase; import org.killbill.billing.util.customfield.ShouldntHappenException; import org.killbill.billing.util.entity.DefaultPagination; import org.killbill.billing.util.entity.Entity; import org.killbill.billing.util.entity.Pagination; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; public class DefaultPaginationHelper { private static final Logger log = LoggerFactory.getLogger(DefaultPaginationHelper.class); public abstract static class EntityPaginationBuilder<E extends Entity, T extends BillingExceptionBase> { public abstract Pagination<E> build(final Long offset, final Long limit, final String pluginName) throws T; } public static <E extends Entity, T extends BillingExceptionBase> Pagination<E> getEntityPaginationFromPlugins(final boolean maxStatsCrossPlugins, final Iterable<String> plugins, final Long offset, final Long limit, final EntityPaginationBuilder<E, T> entityPaginationBuilder) { // Note that we cannot easily do streaming here, since we would have to rely on the statistics // returned by the Pagination objects from the plugins and we probably don't want to do that (if // one plugin gets it wrong, it may starve the others). final List<E> allResults = new LinkedList<E>(); Long totalNbRecords = 0L; Long maxNbRecords = 0L; // Search in all plugins (we treat the full set of results as a union with respect to offset/limit) boolean firstSearch = true; for (final String pluginName : plugins) { try { final Pagination<E> pages; if (allResults.size() >= limit) { // We have enough results, we just keep going (limit 1) to get the stats pages = entityPaginationBuilder.build(firstSearch ? offset : 0L, 1L, pluginName); // Required to close database connections ImmutableList.<E>copyOf(pages); } else { pages = entityPaginationBuilder.build(firstSearch ? offset : 0L, limit - allResults.size(), pluginName); allResults.addAll(ImmutableList.<E>copyOf(pages)); } // Make sure not to start at 0 for subsequent plugins if previous ones didn't yield any result firstSearch = allResults.isEmpty(); totalNbRecords += pages.getTotalNbRecords(); if (!maxStatsCrossPlugins) { maxNbRecords += pages.getMaxNbRecords(); } else { // getPayments and getPaymentMethods return MaxNbRecords across all plugins -- make sure we don't double count maxNbRecords = Math.max(maxNbRecords, pages.getMaxNbRecords()); } } catch (final BillingExceptionBase e) { log.warn("Error while searching plugin='{}'", pluginName, e); // Non-fatal, continue to search other plugins } } return new DefaultPagination<E>(offset, limit, totalNbRecords, maxNbRecords, allResults.iterator()); } public abstract static class SourcePaginationBuilder<O, T extends BillingExceptionBase> { public abstract Pagination<O> build() throws T; } public static <E extends Entity, O, T extends BillingExceptionBase> Pagination<E> getEntityPagination(final Long limit, final SourcePaginationBuilder<O, T> sourcePaginationBuilder, final Function<O, E> function) throws T { final Pagination<O> modelsDao = sourcePaginationBuilder.build(); return new DefaultPagination<E>(modelsDao, limit, Iterators.<E>filter(Iterators.<O, E>transform(modelsDao.iterator(), function), Predicates.<E>notNull())); } public static <E extends Entity, O, T extends BillingExceptionBase> Pagination<E> getEntityPaginationNoException(final Long limit, final SourcePaginationBuilder<O, T> sourcePaginationBuilder, final Function<O, E> function) { try { return getEntityPagination(limit, sourcePaginationBuilder, function); } catch (final BillingExceptionBase e) { throw new ShouldntHappenException("No exception expected" + e); } } }