com.sapienter.jbilling.server.order.UsageBL.java Source code

Java tutorial

Introduction

Here is the source code for com.sapienter.jbilling.server.order.UsageBL.java

Source

/*
 jBilling - The Enterprise Open Source Billing System
 Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde
    
 This file is part of jbilling.
    
 jbilling is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 jbilling is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.
    
 You should have received a copy of the GNU Affero General Public License
 along with jbilling.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sapienter.jbilling.server.order;

import com.sapienter.jbilling.common.SessionInternalError;
import com.sapienter.jbilling.server.item.ItemBL;
import com.sapienter.jbilling.server.item.db.ItemDTO;
import com.sapienter.jbilling.server.order.db.OrderDTO;
import com.sapienter.jbilling.server.order.db.OrderLineDTO;
import com.sapienter.jbilling.server.order.db.UsageDAS;
import com.sapienter.jbilling.server.pluggableTask.OrderPeriodTask;
import com.sapienter.jbilling.server.pluggableTask.TaskException;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskManager;
import com.sapienter.jbilling.server.process.PeriodOfTime;
import com.sapienter.jbilling.server.util.Constants;
import com.sapienter.jbilling.server.util.Context;
import org.apache.log4j.Logger;
import org.joda.time.DateMidnight;
import org.springmodules.cache.CachingModel;
import org.springmodules.cache.FlushingModel;
import org.springmodules.cache.provider.CacheProviderFacade;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * Provides easy access to usage information over the customers natural billing period.
 *
 * @author Brian Cowdery
 * @since 16-08-2010
 */
public class UsageBL {
    private static final Logger LOG = Logger.getLogger(UsageBL.class);

    private static final Integer CURRENT_PERIOD = 1;

    private UsageDAS usageDas;
    private Integer userId;
    private Integer periods;
    private UsagePeriod usagePeriod = null;

    // working order, order in-memory that contains lines applicable to the usage count
    private OrderDTO workingOrder;

    // cache of calculated usage periods
    private CacheProviderFacade cache;
    private CachingModel cacheModel;
    private FlushingModel flushModel;

    /**
     * Construct a UsageBL object to calculate usage for the customers most recent/current
     * period.
     *
     * @param userId user id
     */
    public UsageBL(Integer userId) {
        _init();
        set(userId, CURRENT_PERIOD);
    }

    /**
     * Constructs a UusageBL object to calculate usage for the customers most recent/current
     * period, with a working order that is to be included in the usage counts.
     *
     * @param userId user id
     * @param order working order (order being edited/created).
     */
    public UsageBL(Integer userId, OrderDTO order) {
        _init();
        set(userId, CURRENT_PERIOD);
        setWorkingOrder(order);
    }

    /**
     * Construct a UsageBL object to calculate usage over the given number of
     * periods in the past, where 1 period back is the current period.
     *
     * @param userId user id
     * @param periods number of periods in the past to calculate usage for
     */
    public UsageBL(Integer userId, Integer periods) {
        _init();
        set(userId, periods);
    }

    private void _init() {
        usageDas = new UsageDAS();

        cache = (CacheProviderFacade) Context.getBean(Context.Name.CACHE);
        cacheModel = (CachingModel) Context.getBean(Context.Name.CACHE_MODEL_RW);
        flushModel = (FlushingModel) Context.getBean(Context.Name.CACHE_FLUSH_MODEL_RW);
    }

    public void set(Integer userId, Integer periods) {
        this.userId = userId;
        this.periods = periods;

        usagePeriod = (UsagePeriod) cache.getFromCache(getCacheKey(), cacheModel);

        // could not load period from cache
        if (usagePeriod == null) {

            // new usage period details
            usagePeriod = new UsagePeriod();

            // get main subscription order
            OrderDTO mainOrder = null;
            Integer orderId = new OrderBL().getMainOrderId(userId);
            if (orderId != null)
                mainOrder = new OrderBL(orderId).getEntity();

            if (mainOrder == null)
                LOG.warn("User " + userId + " does not have main subscription order - all usage will be 0!");

            // get billing cycle dates and billing periods for main order.
            if (mainOrder != null) {
                try {
                    Integer entityId = mainOrder.getBaseUserByUserId().getCompany().getId();
                    PluggableTaskManager manager = new PluggableTaskManager(entityId,
                            Constants.PLUGGABLE_TASK_ORDER_PERIODS);
                    OrderPeriodTask periodTask = (OrderPeriodTask) manager.getNextClass();

                    if (periodTask == null)
                        throw new SessionInternalError("OrderPeriodTask not configured!");

                    Date cycleStartDate = periodTask.calculateStart(mainOrder);
                    Date cycleEndDate = periodTask.calculateEnd(mainOrder, new Date(), periods, cycleStartDate);

                    List<PeriodOfTime> billingPeriods = periodTask.getPeriods();

                    if (billingPeriods.isEmpty())
                        throw new SessionInternalError("Could not determine user's billing period!");

                    // populate usage period object for cache
                    usagePeriod.setMainOrder(mainOrder);
                    usagePeriod.setCycleStartDate(cycleStartDate);
                    usagePeriod.setCycleEndDate(cycleEndDate);
                    usagePeriod.setBillingPeriods(billingPeriods);

                    LOG.debug("Caching with key '" + getCacheKey() + "', usage period: " + usagePeriod);
                    cache.putInCache(getCacheKey(), cacheModel, usagePeriod);

                } catch (PluggableTaskException e) {
                    throw new SessionInternalError("Exception occurred retrieving the configured OrderPeriodTask.",
                            e);
                } catch (TaskException e) {
                    throw new SessionInternalError("Exception occurred calculating the customers billing periods.",
                            e);
                }
            }
        } else {
            LOG.debug("Cache hit for '" + getCacheKey() + "', usage period: " + usagePeriod);
        }
    }

    private String getCacheKey() {
        return "user " + userId + " periods " + periods;
    }

    public void invalidateCache() {
        cache.flushCache(flushModel);
    }

    public Integer getUserId() {
        return userId;
    }

    /**
     * Returns the number of periods spanned by this usage calculation inclusive, where 1
     * denotes the current period, 2 is the current period + 1 etc.
     *
     * Example:
     * <lieral>
     *      1 period:
     *      July 1st -> July 30th
     *
     *      2 periods:
     *      June 1st -> July 30th
     *
     *  where July is the current month
     * </literal>
     *
     * @return number of periods spanned by this usage calculation 
     */
    public Integer getPeriods() {
        return periods;
    }

    /**
     * Returns the main subscription order for this customer. The users main subscription
     * order defines the billing cycle dates.
     * 
     * @return customers main subscription order.
     */
    public OrderDTO getMainOrder() {
        return usagePeriod.getMainOrder();
    }

    /**
     * Returns the billing cycle start date for this customer. This is the start date of the very
     * first billing period for this customer, effectively the date the main subscription order
     * became active.
     *
     * @return cycle start date
     */
    public Date getCycleStartDate() {
        return usagePeriod.getCycleStartDate();
    }

    /**
     * Returns the billing cycle end date for this customer. This is the end of of the customers
     * current billing period, effectively the date the main subscription order will become in-active.
     *
     * @return cycle end date
     */
    public Date getCycleEndDate() {
        return usagePeriod.getCycleEndDate();
    }

    /**
     * Returns a list of billing periods of the main subscription order, spanning
     * back N number of periods ({@link #getPeriods()}.
     *
     * @return billing periods
     */
    public List<PeriodOfTime> getBillingPeriods() {
        return usagePeriod.getBillingPeriods();
    }

    /**
     * Returns the start date for the defined period of usage (period of time spanning
     * back N number of periods; {@link #getPeriods()}).
     *
     * @return usage period start date.
     */
    public Date getPeriodStart() {
        // get the first period entry in the list - will be N number of periods in the past
        PeriodOfTime start = usagePeriod.getBillingPeriods().get(0);
        return start.getStart();
    }

    /**
     * Returns the current period end date for this customer. This customer will return
     * end of day today as the end date if the period end date is in the past (can occur
     * if we're processing before the customer's first billing run).
     *
     * @return current period end date
     */
    public Date getPeriodEnd() {
        // get the last period entry in the list - will be the most recent period
        PeriodOfTime end = usagePeriod.getBillingPeriods().get(usagePeriod.getBillingPeriods().size() - 1);

        // end of day today
        DateMidnight today = new DateMidnight().plusDays(1);

        if (new DateMidnight(end.getEnd().getTime()).isBefore(today)) {
            return today.toDate();
        } else {
            return end.getEnd();
        }
    }

    public OrderDTO getWorkingOrder() {
        return workingOrder;
    }

    /**
     * Sets an OrderDTO as the current working order for this usage calculation. The working order's
     * lines will be rolled into the usage calculation.
     *
     * If persisted (has an ID) this order will be excluded from the usage SQL query to prevent
     * the order from being counted twice.
     *
     * @param workingOrder working order to include in usage calculations
     */
    public void setWorkingOrder(OrderDTO workingOrder) {
        this.workingOrder = workingOrder;
    }

    /**
     * Returns the total usage over the set number of periods.
     *
     * @param itemId item id
     * @return usage
     */
    public Usage getItemUsage(Integer itemId) {
        Usage usage;
        if (getMainOrder() != null) {
            Integer workingOrderId = getWorkingOrder() != null ? getWorkingOrder().getId() : null;
            Date startDate = getPeriodStart();
            Date endDate = getPeriodEnd();

            LOG.debug("Fetching usage of item " + itemId + " for " + periods + " period(s), start: " + startDate
                    + ", end: " + endDate);
            usage = usageDas.findUsageByItem(workingOrderId, itemId, userId, startDate, endDate);
        } else {
            LOG.warn("User has no main subscription order billing period, item " + itemId + " usage set to 0");
            usage = new Usage(userId, itemId, null, BigDecimal.ZERO, BigDecimal.ZERO, null, null);
        }

        addWorkingOrder(usage);
        return usage;
    }

    /**
     * Returns the total usage over the set number of periods for this customer and
     * all direct sub-accounts.
     *
     * @param itemId item id
     * @return usage
     */
    public Usage getSubAccountItemUsage(Integer itemId) {
        Usage usage;
        if (getMainOrder() != null) {
            Integer workingOrderId = getWorkingOrder() != null ? getWorkingOrder().getId() : null;
            Date startDate = getPeriodStart();
            Date endDate = getPeriodEnd();

            LOG.debug("Fetching usage (including sub-accounts) of item " + itemId + " for " + periods
                    + " period(s), start: " + startDate + ", end: " + endDate);
            usage = usageDas.findSubAccountUsageByItem(workingOrderId, itemId, userId, startDate, endDate);
        } else {
            LOG.warn("User has no main subscription order billing period, item " + itemId + " usage set to 0");
            usage = new Usage(userId, itemId, null, BigDecimal.ZERO, BigDecimal.ZERO, null, null);
        }

        addWorkingOrder(usage);
        return usage;
    }

    /**
     * Returns the total usage over the set number of periods.
     * 
     * @param itemTypeId item type id
     * @return usage
     */
    public Usage getItemTypeUsage(Integer itemTypeId) {
        Usage usage;
        if (getMainOrder() != null) {
            Integer workingOrderId = getWorkingOrder() != null ? getWorkingOrder().getId() : null;
            Date startDate = getPeriodStart();
            Date endDate = getPeriodEnd();

            LOG.debug("Fetching usage of item type " + itemTypeId + " for " + periods + " period(s), start: "
                    + startDate + ", end: " + endDate);
            usage = usageDas.findUsageByItemType(workingOrderId, itemTypeId, userId, startDate, endDate);
        } else {
            LOG.warn("User has no main subscription order billing period, item type " + itemTypeId
                    + " usage set to 0");
            usage = new Usage(userId, null, itemTypeId, BigDecimal.ZERO, BigDecimal.ZERO, null, null);
        }

        addWorkingOrder(usage);
        return usage;
    }

    /**
     * Returns the total usage over the set number of periods for this customer and
     * all direct sub-accounts.
     *
     * @param itemTypeId item type id
     * @return usage
     */
    public Usage getSubAccountItemTypeUsage(Integer itemTypeId) {
        Usage usage;
        if (getMainOrder() != null) {
            Integer workingOrderId = getWorkingOrder() != null ? getWorkingOrder().getId() : null;
            Date startDate = getPeriodStart();
            Date endDate = getPeriodEnd();

            LOG.debug("Fetching usage (including sub-accounts) of item type " + itemTypeId + " for " + periods
                    + " period(s), start: " + startDate + ", end: " + endDate);
            usage = usageDas.findSubAccountUsageByItemType(workingOrderId, itemTypeId, userId, startDate, endDate);
        } else {
            LOG.warn("User has no main subscription order billing period, item type " + itemTypeId
                    + " usage set to 0");
            usage = new Usage(userId, null, itemTypeId, BigDecimal.ZERO, BigDecimal.ZERO, null, null);
        }

        addWorkingOrder(usage);
        return usage;
    }

    private void addWorkingOrder(Usage usage) {
        if (getWorkingOrder() != null) {
            for (OrderLineDTO line : getWorkingOrder().getLines()) {

                // add matching line items
                if (usage.getItemId() != null && usage.getItemId().equals(line.getItemId()))
                    usage.addLine(line);

                // add matching line items of type
                if (usage.getItemTypeId() != null) {
                    ItemDTO item = new ItemBL(line.getItemId()).getEntity();
                    if (item.hasType(usage.getItemTypeId()))
                        usage.addLine(line);
                }
            }
        }
    }
}