Java tutorial
/* 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); } } } } }