/*
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 java.util.Date;
import java.util.GregorianCalendar;
import java.util.ResourceBundle;
import org.apache.log4j.Logger;
import com.sapienter.jbilling.common.SessionInternalError;
import com.sapienter.jbilling.common.Util;
import com.sapienter.jbilling.server.order.db.OrderDAS;
import com.sapienter.jbilling.server.order.db.OrderDTO;
import com.sapienter.jbilling.server.user.EntityBL;
import com.sapienter.jbilling.server.user.UserBL;
import com.sapienter.jbilling.server.util.Constants;
import com.sapienter.jbilling.server.util.Context;
import com.sapienter.jbilling.server.util.MapPeriodToCalendar;
import com.sapienter.jbilling.server.util.audit.EventLogger;
import com.sapienter.jbilling.server.util.db.CurrencyDTO;
import java.util.List;
import org.springmodules.cache.CachingModel;
import org.springmodules.cache.FlushingModel;
import org.springmodules.cache.provider.CacheProviderFacade;
public class CurrentOrder {
private static final Logger LOG = Logger.getLogger(CurrentOrder.class);
private final EventLogger eLogger = EventLogger.getInstance();
private final Date eventDate;
private final Integer userId;
private final UserBL user;
// current order
private OrderBL order = null;
// cache management
private CacheProviderFacade cache;
private CachingModel cacheModel;
private FlushingModel flushModel;
public CurrentOrder(Integer userId, Date eventDate) {
if (userId == null) throw new IllegalArgumentException("Parameter userId cannot be null!");
if (eventDate == null) throw new IllegalArgumentException("Parameter eventDate cannot be null!");
this.userId = userId;
this.eventDate = eventDate;
this.user = new UserBL(userId);
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);
LOG.debug("Current order constructed with user " + userId + " event date " + eventDate);
}
/**
* Returns the ID of a one-time order, where to add an event.
* Returns null if no applicable order
*
* @return order ID of the current order
*/
public Integer getCurrent() {
// find in the cache
String cacheKey = userId.toString() + Util.truncateDate(eventDate);
Integer retValue = (Integer) cache.getFromCache(cacheKey, cacheModel);
LOG.debug("Retrieved from cache '" + cacheKey + "', order id: " + retValue);
// a hit is only a hit if the order is still active
if (retValue != null && Constants.ORDER_STATUS_ACTIVE.equals(new OrderDAS().find(retValue).getStatusId())) {
LOG.debug("Cache hit for " + retValue);
return retValue;
}
Integer subscriptionId = user.getEntity().getCustomer().getCurrentOrderId();
Integer entityId = null;
Integer currencyId = null;
if (subscriptionId == null) {
return null;
}
// find main subscription order for user
Integer mainOrder;
try {
order = new OrderBL(subscriptionId);
entityId = order.getEntity().getBaseUserByUserId().getCompany().getId();
currencyId = order.getEntity().getCurrencyId();
mainOrder = order.getEntity().getId();
} catch (Exception e) {
throw new SessionInternalError("Error looking for main subscription order",
CurrentOrder.class, e);
}
// loop through future periods until we find a usable current order
int futurePeriods = 0;
boolean orderFound = false;
mainOrder = order.getEntity().getId();
do {
order.set(mainOrder);
final Date newOrderDate = calculateDate(futurePeriods);
LOG.debug("Calculated one timer date: " + newOrderDate + ", for future periods: " + futurePeriods);
if (newOrderDate == null) {
// this is an error, there isn't a good date give the event date and
// the main subscription order
LOG.error("Could not calculate order date for event. Event date is before the order active since date.");
return null;
}
// now that the date is set, let's see if there is a one-time order for that date
boolean somePresent = false;
try {
List<OrderDTO> rows = new OrderDAS().findOneTimersByDate(userId, newOrderDate);
LOG.debug("Found " + rows.size() + " one-time orders for new order date: " + newOrderDate);
for (OrderDTO oneTime : rows) {
somePresent = true;
order.set(oneTime.getId());
if (order.getEntity().getStatusId().equals(Constants.ORDER_STATUS_FINISHED)) {
LOG.debug("Found one timer " + oneTime.getId() + " but status is finished");
} else {
orderFound = true;
LOG.debug("Found existing one-time order");
break;
}
}
} catch (Exception e) {
throw new SessionInternalError("Error looking for one time orders", CurrentOrder.class, e);
}
if (somePresent && !orderFound) {
eLogger.auditBySystem(entityId, userId,
Constants.TABLE_PUCHASE_ORDER,
order.getEntity().getId(),
EventLogger.MODULE_MEDIATION,
EventLogger.CURRENT_ORDER_FINISHED,
subscriptionId, null, null);
} else if (!somePresent) {
// there aren't any one-time orders for this date at all, create one
create(newOrderDate, currencyId, entityId);
orderFound = true;
LOG.debug("Created new one-time order");
}
// non present -> create new one with correct date
// some present & none found -> try next date
// some present & found -> use the found one
futurePeriods++;
} while (!orderFound);
// the result is in 'order'
retValue = order.getEntity().getId();
LOG.debug("Caching order " + retValue + " with key '" + cacheKey + "'");
cache.putInCache(cacheKey, cacheModel, retValue);
LOG.debug("Returning " + retValue);
return retValue;
}
/**
* Assumes that the order has been set with the main subscription order
* @param futurePeriods date for N periods into the future
* @return calculated period date for N future periods
*/
private Date calculateDate(int futurePeriods) {
GregorianCalendar cal = new GregorianCalendar();
// start from the active since if it is there, otherwise the create time
final Date startingTime = order.getEntity().getActiveSince() == null
? order.getEntity().getCreateDate()
: order.getEntity().getActiveSince();
// calculate the event date with the added future periods
Date actualEventDate = eventDate;
cal.setTime(actualEventDate);
for (int f = 0; f < futurePeriods; f++) {
cal.add(MapPeriodToCalendar.map(order.getEntity().getOrderPeriod().getPeriodUnit().getId()),
order.getEntity().getOrderPeriod().getValue());
}
actualEventDate = cal.getTime();
// is the starting date beyond the time frame of the main order?
if (order.getEntity().getActiveSince() != null && actualEventDate.before(order.getEntity().getActiveSince())) {
LOG.error("The event for date " + actualEventDate
+ " can not be assigned for order " + order.getEntity().getId()
+ " active since " + order.getEntity().getActiveSince());
return null;
}
Date newOrderDate = startingTime;
cal.setTime(startingTime);
while (cal.getTime().before(actualEventDate)) {
newOrderDate = cal.getTime();
cal.add(MapPeriodToCalendar.map(order.getEntity().getOrderPeriod().getPeriodUnit().getId()),
order.getEntity().getOrderPeriod().getValue());
}
// is the found date beyond the time frame of the main order?
if (order.getEntity().getActiveUntil() != null && newOrderDate.after(order.getEntity().getActiveUntil())) {
LOG.error("The event for date " + actualEventDate
+ " can not be assigned for order " + order.getEntity().getId()
+ " active until " + order.getEntity().getActiveUntil());
return null;
}
return newOrderDate;
}
/**
* Creates a new one-time order for the given active since date.
* @param activeSince active since date
* @param currencyId currency of order
* @param entityId company id of order
* @return new order
*/
public Integer create(Date activeSince, Integer currencyId, Integer entityId) {
OrderDTO currentOrder = new OrderDTO();
currentOrder.setCurrency(new CurrencyDTO(currencyId));
// notes
try {
EntityBL entity = new EntityBL(entityId);
ResourceBundle bundle = ResourceBundle.getBundle("entityNotifications", entity.getLocale());
currentOrder.setNotes(bundle.getString("order.current.notes"));
} catch (Exception e) {
throw new SessionInternalError("Error setting the new order notes", CurrentOrder.class, e);
}
currentOrder.setActiveSince(activeSince);
// create the order
if (order == null) {
order = new OrderBL();
}
order.set(currentOrder);
order.addRelationships(userId, Constants.ORDER_PERIOD_ONCE, currencyId);
return order.create(entityId, null, currentOrder);
}
}