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

org.dbflute.cbean.paging.PagingInvoker Maven / Gradle / Ivy

There is a newer version: 1.2.8
Show newest version
/*
 * Copyright 2014-2021 the original author or authors.
 *
 * 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.dbflute.cbean.paging;

import java.util.List;

import org.dbflute.cbean.ConditionBean;
import org.dbflute.cbean.result.PagingResultBean;
import org.dbflute.cbean.result.ResultBeanBuilder;
import org.dbflute.exception.DangerousResultSizeException;
import org.dbflute.exception.PagingOverSafetySizeException;
import org.dbflute.exception.PagingStatusInvalidException;
import org.dbflute.helper.message.ExceptionMessageBuilder;
import org.dbflute.jdbc.ManualThreadDataSourceHandler;
import org.dbflute.system.DBFluteSystem;

/**
 * The invoker of paging.
 * @param  The type of entity.
 * @author jflute
 */
public class PagingInvoker {

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    protected final String _tableDbName;

    // ===================================================================================
    //                                                                         Constructor
    //                                                                         ===========
    public PagingInvoker(String tableDbName) {
        _tableDbName = tableDbName;
    }

    // ===================================================================================
    //                                                                              Invoke
    //                                                                              ======
    /**
     * Invoke select-page by handler.
     * @param handler The handler of paging. (NotNull)
     * @return The result bean of paging. (NotNull)
     * @throws org.dbflute.exception.PagingStatusInvalidException When the paging status is invalid.
     * @throws org.dbflute.exception.PagingOverSafetySizeException When the paging is over safety size.
     */
    public PagingResultBean invokePaging(PagingHandler handler) {
        assertObjectNotNull("handler", handler);
        final PagingBean pagingBean = handler.getPagingBean();
        assertObjectNotNull("handler.getPagingBean()", pagingBean);
        if (!pagingBean.isFetchScopeEffective()) {
            throwPagingStatusInvalidException(pagingBean);
        }
        final ResultBeanBuilder builder = createResultBeanBuilder();
        final boolean useManualThreadDataSource = isUseManualThreadDataSource();
        if (useManualThreadDataSource) {
            ManualThreadDataSourceHandler.prepareDataSourceHandler();
        }
        try {
            final InvocationResultResource resource = doPaging(handler, pagingBean, builder);
            final int allRecordCount = resource.getAllRecordCount();
            final List selectedList = resource.getSelectedList();
            final PagingResultBean rb = builder.buildPagingOfPaging(pagingBean, allRecordCount, selectedList);
            if (pagingBean.canPagingReSelect() && isNecessaryToReadPageAgain(rb)) {
                return reselect(handler, pagingBean, builder, rb);
            } else {
                return rb;
            }
        } finally {
            pagingBean.xsetPaging(true); // restore its paging state finally
            if (useManualThreadDataSource) {
                ManualThreadDataSourceHandler.closeDataSourceHandler();
            }
        }
    }

    protected InvocationResultResource doPaging(PagingHandler handler, PagingBean pagingBean,
            ResultBeanBuilder builder) {
        final int safetyMaxResultSize = pagingBean.getSafetyMaxResultSize();
        final int allRecordCount;
        final List selectedList;
        if (pagingBean.canPagingCountLater()) { // faster when last page selected (contains zero record)
            selectedList = executePaging(handler);
            if (isCurrentLastPage(selectedList, pagingBean)) {
                allRecordCount = deriveAllRecordCountByLastPage(selectedList, pagingBean);
            } else {
                allRecordCount = executeCount(handler); // count later
            }
            checkSafetyResultIfNeeds(safetyMaxResultSize, allRecordCount);
        } else { // faster when zero record selected
            // basically main here because it has been used for a long time
            allRecordCount = executeCount(handler);
            checkSafetyResultIfNeeds(safetyMaxResultSize, allRecordCount);
            if (allRecordCount == 0) {
                selectedList = builder.buildEmptyListOfPaging(pagingBean);
            } else {
                selectedList = executePaging(handler);
            }
        }
        final InvocationResultResource resource = new InvocationResultResource();
        resource.setAllRecordCount(allRecordCount);
        resource.setSelectedList(selectedList);
        return resource;
    }

    protected static class InvocationResultResource {
        protected int _allRecordCount;
        protected List _selectedList;

        public int getAllRecordCount() {
            return _allRecordCount;
        }

        public void setAllRecordCount(int allRecordCount) {
            _allRecordCount = allRecordCount;
        }

        public List getSelectedList() {
            return _selectedList;
        }

        public void setSelectedList(List selectedList) {
            _selectedList = selectedList;
        }
    }

    /**
     * Create the builder of result bean.
     * @return The builder of result bean. (NotNull)
     */
    protected ResultBeanBuilder createResultBeanBuilder() {
        return new ResultBeanBuilder(_tableDbName);
    }

    protected int executeCount(PagingHandler handler) {
        return handler.count();
    }

    protected List executePaging(PagingHandler handler) {
        return handler.paging();
    }

    protected PagingResultBean reselect(PagingHandler handler, PagingBean pagingBean, ResultBeanBuilder builder,
            PagingResultBean rb) {
        pagingBean.xfetchPage(rb.getAllPageCount());
        final InvocationResultResource resource = doPaging(handler, pagingBean, builder);
        final int allRecordCount = resource.getAllRecordCount();
        final List selectedList = resource.getSelectedList();
        return builder.buildPagingOfPaging(pagingBean, allRecordCount, selectedList);
    }

    /**
     * Is the current page is last page?
     * @param selectedList The selected list. (NotNull)
     * @param pagingBean The bean of paging. (NotNull)
     * @return The determination, true or false.
     */
    protected boolean isCurrentLastPage(List selectedList, PagingBean pagingBean) {
        if (selectedList.size() == 0 && pagingBean.getFetchPageNumber() > 1) {
            return false; // because of unknown all record count (re-selected later)
        }
        // /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
        // It returns true if the size of list is under fetch size(page size).
        // (contains the size is zero and first page is target)
        // 
        // {For example}
        // If the fetch size is 20 and the size of selected list is 19 or less,
        // the current page must be last page(contains when only one page exists). 
        // it is NOT necessary to read count because the 19 is the hint to derive all record count.
        // 
        // If the fetch size is 20 and the size of selected list is 20,
        // it is necessary to read count because we cannot know whether the next pages exist or not.
        // - - - - - - - - - -/
        return selectedList.size() <= (pagingBean.getFetchSize() - 1);
    }

    /**
     * Derive all record count by last page.
     * @param selectedList The selected list. (NotNull)
     * @param pagingBean The bean of paging. (NotNull)
     * @return Derived all record count.
     */
    protected int deriveAllRecordCountByLastPage(List selectedList, PagingBean pagingBean) {
        int baseSize = (pagingBean.getFetchPageNumber() - 1) * pagingBean.getFetchSize();
        return baseSize + selectedList.size();
    }

    /**
     * Is it necessary to read page again?
     * @param rb The result bean of paging. (NotNull)
     * @return The determination, true or false.
     */
    protected boolean isNecessaryToReadPageAgain(PagingResultBean rb) {
        return rb.getAllRecordCount() > 0 && rb.getSelectedList().isEmpty();
    }

    /**
     * Check whether the count of all records is safety or not if it needs.
     * @param safetyMaxResultSize The max size of safety result.
     * @param allRecordCount The count of all records.
     * @throws DangerousResultSizeException When the count of all records is dangerous.
     */
    protected void checkSafetyResultIfNeeds(int safetyMaxResultSize, int allRecordCount) {
        if (safetyMaxResultSize > 0 && allRecordCount > safetyMaxResultSize) {
            throwPagingOverSafetySizeException(safetyMaxResultSize, allRecordCount);
        }
    }

    protected boolean isUseManualThreadDataSource() {
        return true; // basically for MySQL's found_rows() when no transaction
    }

    protected void throwPagingStatusInvalidException(PagingBean pagingBean) {
        final boolean cbean = pagingBean instanceof ConditionBean;
        final String name = cbean ? "condition-bean" : "parameter-bean";
        final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
        br.addNotice("The status of paging was INVALID. (paging parameters was not found)");
        br.addItem("Advice");
        br.addElement("Confirm your logic for paging of " + name + ".");
        br.addElement("Paging execution needs paging parameters 'pageSize' and 'pageNumber'.");
        br.addElement("For example:");
        br.addElement("  (x):");
        if (cbean) {
            br.addElement("    MemberCB cb = new MemberCB();");
            br.addElement("    cb.query().set...;");
            br.addElement("    ... = memberBhv.selectPage(cb);");
        } else {
            br.addElement("    SimpleMemberPmb pmb = new SimpleMemberPmb();");
            br.addElement("    pmb.set...;");
            br.addElement("    ... = memberBhv.outsideSql().manualPaging().selectPage(...);");
        }
        br.addElement("  (o):");
        if (cbean) {
            br.addElement("    MemberCB cb = new MemberCB();");
            br.addElement("    cb.query().set...;");
            br.addElement("    cb.paging(20, 2); // *Point!");
            br.addElement("    ... = memberBhv.selectPage(cb);");
        } else {
            br.addElement("    SimpleMemberPmb pmb = new SimpleMemberPmb();");
            br.addElement("    pmb.set...;");
            br.addElement("    pmb.paging(20, 2); // *Point!");
            br.addElement("    ... = memberBhv.outsideSql().manualPaging().selectPage(...);");
        }
        br.addItem("PagingBean");
        br.addElement(pagingBean);
        final String msg = br.buildExceptionMessage();
        throw new PagingStatusInvalidException(msg);
    }

    protected void throwPagingOverSafetySizeException(int safetyMaxResultSize, int allRecordCount) {
        // here simple message because an entry method catches this
        String msg = "The paging was over the specified safety size:";
        msg = msg + " " + allRecordCount + " > " + safetyMaxResultSize;
        throw new PagingOverSafetySizeException(msg, safetyMaxResultSize, allRecordCount);
    }

    // ===================================================================================
    //                                                                      General Helper
    //                                                                      ==============
    protected String ln() {
        return DBFluteSystem.ln();
    }

    // ===================================================================================
    //                                                                       Assert Helper
    //                                                                       =============
    /**
     * Assert that the object is not null.
     * @param variableName The check name of variable for message. (NotNull)
     * @param value The checked value. (NotNull)
     * @throws IllegalArgumentException When the argument is null.
     */
    protected void assertObjectNotNull(String variableName, Object value) {
        if (variableName == null) {
            String msg = "The value should not be null: variableName=null value=" + value;
            throw new IllegalArgumentException(msg);
        }
        if (value == null) {
            String msg = "The value should not be null: variableName=" + variableName;
            throw new IllegalArgumentException(msg);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy