/*
* Copyright 2010-2012 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 com.ning.billing.analytics.api.user;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ning.billing.account.api.Account;
import com.ning.billing.analytics.BusinessAccountDao;
import com.ning.billing.analytics.BusinessInvoiceDao;
import com.ning.billing.analytics.BusinessInvoicePaymentDao;
import com.ning.billing.analytics.BusinessOverdueStatusDao;
import com.ning.billing.analytics.BusinessSubscriptionTransitionDao;
import com.ning.billing.analytics.BusinessTagDao;
import com.ning.billing.analytics.api.TimeSeriesData;
import com.ning.billing.analytics.dao.AnalyticsDao;
import com.ning.billing.analytics.model.BusinessAccount;
import com.ning.billing.analytics.model.BusinessAccountTag;
import com.ning.billing.analytics.model.BusinessInvoice;
import com.ning.billing.analytics.model.BusinessInvoiceItem;
import com.ning.billing.analytics.model.BusinessInvoicePayment;
import com.ning.billing.analytics.model.BusinessOverdueStatus;
import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.junction.api.Blockable.Type;
import com.ning.billing.payment.api.Payment;
import com.ning.billing.payment.api.PaymentApi;
import com.ning.billing.payment.api.PaymentApiException;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.InternalCallContext;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
import com.ning.billing.util.callcontext.TenantContext;
import com.ning.billing.util.dao.ObjectType;
import com.ning.billing.util.svcapi.entitlement.EntitlementInternalApi;
import com.ning.billing.util.svcapi.tag.TagInternalApi;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
public class DefaultAnalyticsUserApi implements AnalyticsUserApi {
private static final Logger log = LoggerFactory.getLogger(DefaultAnalyticsUserApi.class);
private final AnalyticsDao analyticsDao;
private final BusinessSubscriptionTransitionDao bstDao;
private final BusinessAccountDao bacDao;
private final BusinessInvoiceDao invoiceDao;
private final BusinessOverdueStatusDao bosDao;
private final BusinessInvoicePaymentDao bipDao;
private final BusinessTagDao tagDao;
private final EntitlementInternalApi entitlementInternalApi;
private final PaymentApi paymentApi;
private final TagInternalApi tagInternalApi;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
public DefaultAnalyticsUserApi(final AnalyticsDao analyticsDao,
final BusinessSubscriptionTransitionDao bstDao,
final BusinessAccountDao bacDao,
final BusinessInvoiceDao invoiceDao,
final BusinessOverdueStatusDao bosDao,
final BusinessInvoicePaymentDao bipDao,
final BusinessTagDao tagDao,
final EntitlementInternalApi entitlementInternalApi,
final PaymentApi paymentApi,
final TagInternalApi tagInternalApi,
final InternalCallContextFactory internalCallContextFactory) {
this.analyticsDao = analyticsDao;
this.bstDao = bstDao;
this.bacDao = bacDao;
this.invoiceDao = invoiceDao;
this.bosDao = bosDao;
this.bipDao = bipDao;
this.tagDao = tagDao;
this.entitlementInternalApi = entitlementInternalApi;
this.paymentApi = paymentApi;
this.tagInternalApi = tagInternalApi;
this.internalCallContextFactory = internalCallContextFactory;
}
@Override
public TimeSeriesData getAccountsCreatedOverTime(final TenantContext context) {
return analyticsDao.getAccountsCreatedOverTime(internalCallContextFactory.createInternalTenantContext(context));
}
@Override
public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug, final TenantContext context) {
return analyticsDao.getSubscriptionsCreatedOverTime(productType, slug, internalCallContextFactory.createInternalTenantContext(context));
}
@Override
public void rebuildAnalyticsForAccount(final Account account, final CallContext context) {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(context);
// Update the BAC row
bacDao.accountUpdated(account.getId(), internalCallContext);
// Update BST for all bundles
final Set<UUID> bundleIds = updateBST(account, internalCallContext);
// Update BIN and BII for all invoices
invoiceDao.rebuildInvoicesForAccount(account.getId(), internalCallContext);
// Update BIP for all invoices
try {
updateBIP(account, context, internalCallContext);
} catch (PaymentApiException e) {
// Log and ignore
log.warn(e.toString());
}
// Update BOS for all bundles (only blockable supported today)
// TODO: support other blockables
for (final UUID bundleId : bundleIds) {
bosDao.overdueStatusChanged(Type.SUBSCRIPTION_BUNDLE, bundleId, internalCallContext);
}
// Update bac_tags
// TODO: refresh all tags
updateTags(account, internalCallContext);
}
private Set<UUID> updateBST(final Account account, final InternalCallContext internalCallContext) {
// Find the current state of bundles in entitlement
final Collection<UUID> entitlementBundlesId = Collections2.transform(entitlementInternalApi.getBundlesForAccount(account.getId(), internalCallContext),
new Function<SubscriptionBundle, UUID>() {
@Override
public UUID apply(@Nullable final SubscriptionBundle input) {
if (input == null) {
return null;
} else {
return input.getId();
}
}
});
// Find the current state of bundles in analytics
final Collection<UUID> analyticsBundlesId = Collections2.transform(analyticsDao.getTransitionsForAccount(account.getExternalKey(), internalCallContext),
new Function<BusinessSubscriptionTransition, UUID>() {
@Override
public UUID apply(@Nullable final BusinessSubscriptionTransition input) {
if (input == null) {
return null;
} else {
return input.getBundleId();
}
}
});
// Update BST for all bundles found
final Set<UUID> bundlesId = new HashSet<UUID>();
bundlesId.addAll(entitlementBundlesId);
bundlesId.addAll(analyticsBundlesId);
for (final UUID bundleId : bundlesId) {
bstDao.rebuildTransitionsForBundle(bundleId, internalCallContext);
}
return bundlesId;
}
private void updateBIP(final Account account, final TenantContext tenantContext, final InternalCallContext internalCallContext) throws PaymentApiException {
final List<Payment> accountPayments = paymentApi.getAccountPayments(account.getId(), tenantContext);
final Map<UUID, Payment> payments = new HashMap<UUID, Payment>();
for (final Payment payment : accountPayments) {
payments.put(payment.getId(), payment);
}
// Find the current state of payments in payment
final Collection<UUID> paymentPaymentsId = Collections2.transform(accountPayments,
new Function<Payment, UUID>() {
@Override
public UUID apply(@Nullable final Payment input) {
if (input == null) {
return null;
} else {
return input.getId();
}
}
});
// Find the current state of payments in analytics
final Collection<UUID> analyticsPaymentsId = Collections2.transform(analyticsDao.getInvoicePaymentsForAccountByKey(account.getExternalKey(), internalCallContext),
new Function<BusinessInvoicePayment, UUID>() {
@Override
public UUID apply(@Nullable final BusinessInvoicePayment input) {
if (input == null) {
return null;
} else {
return input.getPaymentId();
}
}
});
// Update BIP for all payments found
final Set<UUID> paymentsId = new HashSet<UUID>();
paymentsId.addAll(paymentPaymentsId);
paymentsId.addAll(analyticsPaymentsId);
for (final UUID paymentId : paymentsId) {
final Payment paymentInfo = payments.get(paymentId);
bipDao.invoicePaymentPosted(paymentInfo.getAccountId(),
paymentInfo.getId(),
paymentInfo.getExtFirstPaymentIdRef(),
paymentInfo.getExtSecondPaymentIdRef(),
paymentInfo.getPaymentStatus().toString(),
internalCallContext);
}
}
private void updateTags(final Account account, final InternalCallContext internalCallContext) {
// Find the current state of tags from util
final Collection<String> utilTags = Collections2.transform(tagInternalApi.getTags(account.getId(), ObjectType.ACCOUNT, internalCallContext).keySet(),
new Function<String, String>() {
@Override
public String apply(@Nullable final String input) {
return input;
}
});
// Find the current state of tags in analytics
final Collection<String> analyticsTags = Collections2.transform(analyticsDao.getTagsForAccount(account.getExternalKey(), internalCallContext),
new Function<BusinessAccountTag, String>() {
@Override
public String apply(@Nullable final BusinessAccountTag input) {
if (input == null) {
return null;
} else {
return input.getName();
}
}
});
// Remove non existing tags
for (final String tag : Sets.difference(new HashSet<String>(analyticsTags), new HashSet<String>(utilTags))) {
tagDao.tagRemoved(ObjectType.ACCOUNT, account.getId(), tag, internalCallContext);
}
// Add missing ones
for (final String tag : Sets.difference(new HashSet<String>(utilTags), new HashSet<String>(analyticsTags))) {
tagDao.tagAdded(ObjectType.ACCOUNT, account.getId(), tag, internalCallContext);
}
}
// Note: the following is not exposed in api yet, as the models need to be extracted first
public BusinessAccount getAccountByKey(final String accountKey, final TenantContext context) {
return analyticsDao.getAccountByKey(accountKey, internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessSubscriptionTransition> getTransitionsForBundle(final String externalKey, final TenantContext context) {
return analyticsDao.getTransitionsByKey(externalKey, internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessInvoice> getInvoicesForAccount(final String accountKey, final TenantContext context) {
return analyticsDao.getInvoicesByKey(accountKey, internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessAccountTag> getTagsForAccount(final String accountKey, final TenantContext context) {
return analyticsDao.getTagsForAccount(accountKey, internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessOverdueStatus> getOverdueStatusesForBundle(final String externalKey, final TenantContext context) {
return analyticsDao.getOverdueStatusesForBundleByKey(externalKey, internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessInvoiceItem> getInvoiceItemsForInvoice(final UUID invoiceId, final TenantContext context) {
return analyticsDao.getInvoiceItemsForInvoice(invoiceId.toString(), internalCallContextFactory.createInternalTenantContext(context));
}
public List<BusinessInvoicePayment> getInvoicePaymentsForAccount(final String accountKey, final TenantContext context) {
return analyticsDao.getInvoicePaymentsForAccountByKey(accountKey, internalCallContextFactory.createInternalTenantContext(context));
}
}