Java tutorial
package com.comcast.locker.gateway.xgateway.service; import com.comcast.common.Crn; import com.comcast.common.Crn.Prefix; import com.comcast.config.Configuration; import com.comcast.locker.api.account.AccountId; import com.comcast.locker.api.asset.AssetId; import com.comcast.locker.api.asset.Right; import com.comcast.locker.api.service.LockerService; import com.comcast.locker.api.tx.Transaction; import com.comcast.locker.gateway.services.GatewayService; import com.comcast.locker.gateway.services.UnuService; import com.comcast.locker.gateway.util.SettingUtils; import com.comcast.locker.gateway.xgateway.XGatewaySettings; import com.comcast.locker.gateway.xgateway.domain.ValidateLicenseRequest; import com.comcast.locker.gateway.xgateway.domain.ValidateLicenseResponse; import com.comcast.locker.gateway.xgateway.utils.XGatewayUtils; import com.comcast.title.TitlePaid; import com.comcast.title.Titles; import com.comcast.title.encoding.Api.Rental; import com.comcast.title.encoding.Api.Title; import com.comcast.title.encoding.Api.TitlePlayability; import com.comcast.util.Pair; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * Validate License Controller validates whether or not a given account * has a valid right to play a given asset. * <p> * If the account has a valid rental for it, the controller returns an entitlement. * If the account has the right to play the asset, but no rental, it will attempt * to rent the asset for the account. * Otherwise, it will return the appropriate reason the account cannot play the asset. */ public class ValidateLicenseController { private static final Logger LOG = LoggerFactory.getLogger(ValidateLicenseController.class); private static final String ENTITLEMENT_ID_PREFIX = "comcast:locker:xgateway:entitlement-id:"; private static final String UNKNOWN_CLIENT_PART = "UNKNOWN"; private static final ImmutableMap<String, String> CLIENT_OVERRIDES = new ImmutableMap.Builder<String, String>() .put("xcal:Xi3", "XRE:Xi3").put("xcal:X1", "XRE:X1").build(); private static final long OWED_ASSET_EXPIRATION_TIME = 0; private final GatewayService gatewayService; private final UnuService unuService; private final LockerService lockerService; private final String cempTransactionHandler; private final long ensureBillingResumePointThreshold; private final long spdLockerTimeTolerance; private final long gracePeriodAllowance; private final String enforceHdcpValue; private final int minimumSecurityLevel; private final int minimumRuntimeSecurityLevel; private final boolean awaitEvidenceOfPlay; private final Map<String, String> extensions; public ValidateLicenseController(Configuration configuration, GatewayService gatewayService, LockerService lockerService, String cempTransactionHandler, UnuService unuService) { checkNotNull(gatewayService, "The 'gatewayService' cannot be null."); checkNotNull(unuService, "The 'unuService' cannot be null."); checkNotNull(lockerService, "The 'lockerService' cannot be null."); checkNotNull(cempTransactionHandler, "The 'cempTransactionHandler' cannot be null."); checkNotNull(configuration, "The 'configuration' cannot be null."); ensureBillingResumePointThreshold = configuration .getAsLong(XGatewaySettings.VALIDATE_LICENSE_ENSURE_BILLED_THESHOLD); spdLockerTimeTolerance = configuration .getAsLong(XGatewaySettings.VALIDATE_LICENSE_LICENSE_WINDOW_TOLERANCE); gracePeriodAllowance = configuration.getAsLong(XGatewaySettings.VALIDATE_LICENSE_GRACE_PERIOD_ALLOWANCE); enforceHdcpValue = configuration.get(XGatewaySettings.VALIDATE_LICENSE_ENFORCE_HDCP_VALUE); minimumSecurityLevel = configuration.getAsInt(XGatewaySettings.VALIDATE_LICENSE_MINIMUM_SECURITY_LEVEL); minimumRuntimeSecurityLevel = configuration .getAsInt(XGatewaySettings.VALIDATE_LICENSE_MINIMUM_RUNTIME_SECURITY_LEVEL); extensions = SettingUtils .settingValueAsMap(configuration.get(XGatewaySettings.VALIDATE_LICENSE_RIGHT_EXTENSIONS)); awaitEvidenceOfPlay = configuration .getAsBoolean(XGatewaySettings.PURCHASE_AWAIT_EVIDENCE_OF_PLAY_BEFORE_BILLING); this.gatewayService = gatewayService; this.unuService = unuService; this.lockerService = lockerService; this.cempTransactionHandler = cempTransactionHandler; } /** * "Executes" the use case by: * Looking up assetId and accountIds, then checking for titlePlayability from UNU * Based on title playability decision, follow the following logic: * 1) If bad decision - fail * 2) If rental - success * 3) If not rental * 3a) check for rights - if owned, success (and add to SPD) * 3b) if free/gated - success (and add to SPD) * 3c) if transaction - check locker * 3c)a) if locker, success (and add to spd) * 3c)b) if not in locker - error 'Not Purchased' */ public ValidateLicenseResponse validateLicense(ValidateLicenseRequest validateLicenseRequest) { checkArgument(validateLicenseRequest != null, "The 'purchaseRequest' cannot be null."); long now = System.currentTimeMillis(); Pair<Crn, AccountId> accountPair = getAccountId(validateLicenseRequest); AccountId accountId = accountPair.getSecond(); AssetId assetId = getAssetId(validateLicenseRequest); String client = getClient(validateLicenseRequest); String accountNumber = gatewayService.resolveAccountNumberFromLockerAccountId(accountId); TitlePaid paid = gatewayService.resolveTitlePaidFromLockerAssetId(assetId); //step 1, check on title playability TitlePlayability playability = unuService.validatePlayEligibility(paid, accountNumber, client); //validate decisions and throw if bad decision validateAvailability(playability); Title title = playability.getTitle(); //step 2, check if the title is in SPD (if it is it'll have a rental object attached) if (title.hasRental()) { //if we have a rental, use it's expiration date and it's start date for the //entitlement Rental rental = title.getRental(); ensureTitleWasBilled(client, rental, title, accountId, assetId, now, new Crn(Prefix.TITLE_PAID, paid.getRawPaid()), accountPair.getFirst()); long expirationDate = rental.getExpireDateTime(); return generateResponse(accountNumber, title, new Date(rental.getRentDateTime()), new Date(expirationDate), title.hasRight()); } //if user is entitled to the asset because it's free, gated by something the user //is entitled to, or if the user owns it if (Titles.titleIsFree(title) || Titles.titleIsGatedAndSubscriberIsEntitled(title) || title.hasRight()) { return generateResponse(accountNumber, title, new Date(now), null, title.hasRight()); } List<Right> lockerRights = lockerService.getAssetRightsByAccountAndAsset(accountId, assetId); for (Right right : lockerRights) { if (right.isInWindow(now) && right.isPlayable()) { //we bought or rented this and the locker knows about it but not saved programs //lets let them watch it and try to save it as well to make this easier the //next time around return generateResponse(accountNumber, title, right.getStartTime(), right.getEndTime(), right.isPermanent()); } } if (Titles.titleIsElectronicSellThrough(title)) { throw new AssetNotPurchasedException("Asset not Purchased"); } else { throw new AssetNotPurchasedException("Asset not Rented"); } } @VisibleForTesting String getClient(ValidateLicenseRequest request) { String provider = request.getSubjectInfo().getProvider(); String model = request.getSubjectInfo().getDeviceModel(); String clientKey; if (model == null && provider == null) { //if both empty, keep it short and sweet clientKey = UNKNOWN_CLIENT_PART; } else { clientKey = StringUtils.defaultIfEmpty(provider, UNKNOWN_CLIENT_PART) + ':' + StringUtils.defaultIfEmpty(model, UNKNOWN_CLIENT_PART); } String client = CLIENT_OVERRIDES.get(clientKey); if (client == null) { LOG.warn("Received unexpected client fields " + clientKey); return clientKey; } return client; } private ValidateLicenseResponse generateResponse(String accountNumber, Title title, Date availableDate, Date expirationDateFromStorage, boolean owned) { Date expirationDate = expirationDateFromStorage; if (title.getAllowedRentalDurationInSeconds() == 0) { expirationDate = new Date(availableDate.getTime() + XGatewayUtils.parseDuration(title.getRunTime())); } else if (owned) { expirationDate = new Date(OWED_ASSET_EXPIRATION_TIME); } if (expirationDate == null) { expirationDate = XGatewayUtils.calculateEndTime(availableDate.getTime(), title.getAllowedRentalDurationInSeconds()); } ValidateLicenseResponse res = new ValidateLicenseResponse(); res.setEntitlementId(generateEntitlementId(accountNumber, title.getPaid(), availableDate.getTime())); res.setExpirationDate(expirationDate); res.setEnforceHdcp(enforceHdcpValue); res.setMinimumSecurityLevel(minimumSecurityLevel); res.setMinimumRuntimeSecurityLevel(minimumRuntimeSecurityLevel); res.setAvailableDate(availableDate); res.setRightsExtensions(extensions); return res; } /** * {@code accountNumber + assetId + rentalStartTime}. * Only included in successful responses. * <br/> * e.g. * <code> * comcast:locker:xgateway:entitlement-id:9645225921090-hbo.comTITL00000000000000001-1213087203740234 * </code> */ private String generateEntitlementId(String accountNumber, String paid, long rentalStartTime) { StringBuilder sb = new StringBuilder(ENTITLEMENT_ID_PREFIX); sb.append(accountNumber).append('-').append(paid).append('-').append(rentalStartTime); return sb.toString(); } /** * Retrieves the accountId from the ValidateLicenseRequest, first attempting to use the * CET Billing ID, but then trying with the AuthGuid if the Billing Id is blank */ private Pair<Crn, AccountId> getAccountId(ValidateLicenseRequest request) { String cetBillingId = request.getSubjectInfo().getCetBillingId(); if (!StringUtils.isEmpty(cetBillingId)) { return new Pair<>(new Crn(Prefix.CET_ACCOUNT_NUMBER, cetBillingId), gatewayService.resolveLockerAccountIdFromAccountNumber(cetBillingId)); } else if (!StringUtils.isEmpty(request.getSubjectInfo().getCetAuthGuid())) { String authGuid = request.getSubjectInfo().getCetAuthGuid(); return new Pair<>(new Crn(Prefix.CET_AUTH_GUID, authGuid), gatewayService.resolveLockerAccountIdFromAuthGuid(authGuid)); } else if (!StringUtils.isEmpty(request.getSubjectInfo().getPartnerBillingId())) { String partnerBillingId = request.getSubjectInfo().getPartnerBillingId(); return new Pair<>(new Crn(Prefix.CET_ACCOUNT_NUMBER, partnerBillingId), gatewayService.resolveLockerAccountIdFromAccountNumber(partnerBillingId)); } else { String partnerAuthGuid = request.getSubjectInfo().getPartnerAuthGuid(); return new Pair<>(new Crn(Prefix.CET_AUTH_GUID, partnerAuthGuid), gatewayService.resolveLockerAccountIdFromAuthGuid(partnerAuthGuid)); } } /** * Ensures the title was billed using the following logic: * Checks XGateway Configuration for resumepoint threshold for billing. * If the title's resume point doesn't have a resume point greater than the * threshold, we check the locker for any rights in window, if no rights, * we re-add the transaction to ensure billing * <p> * If the threshold is set to -1, that means we always check the locker */ private void ensureTitleWasBilled(String client, Rental rental, Title title, AccountId accountId, AssetId assetId, long now, Crn assetCrn, Crn accountCrn) { if (title.hasRight() || Titles.titleIsElectronicSellThrough(title) || Titles.titleIsFree(title) || Titles.titleIsGatedAndSubscriberIsEntitled(title)) { //if a title is in SPD, then we don't rebill (or first bill) for ESTs, Free assets or gated assets return; } if (ensureBillingResumePointThreshold == -1 || rental.getResumePointInMillis() <= ensureBillingResumePointThreshold) { //if the locker expired and we're in the grace period, allow a tolerance of the gracePeriodAllowance, //otherwise, if we're in the rental window, only allow for differences in the times of //the different servers //if rental is expired, but we made it here, that means we're in grace period //grace periods have a larger allowance for locker/spd differences long tolerance = rental.getExpireDateTime() < now ? gracePeriodAllowance : spdLockerTimeTolerance; List<Right> rights = lockerService.getAssetRightsByAccountAndAsset(accountId, assetId); for (Right right : rights) { if (right.isInWindow(now, tolerance)) { return; } } //if we're here, we don't have a right, we're not in the locker //and we haven't watched it long enough to avoid all this, create a new transaction createPurchaseTransactionInLocker(client, title, accountId, assetId, assetCrn, accountCrn); } } private AssetId getAssetId(ValidateLicenseRequest request) { String providerId = request.getContentMetadataInfo().getProviderId(); String assetId = request.getContentMetadataInfo().getTitleAssetId(); return gatewayService.resolveLockerAssetIdFromTitlePaid(new TitlePaid(providerId, assetId)); } private Pair<Transaction, Date> createPurchaseTransactionInLocker(String client, Title title, AccountId accountId, AssetId assetId, Crn assetCrn, Crn accountCrn) { Pair<Transaction, Date> trans = XGatewayUtils.createPurchaseTransactionForLocker(client, cempTransactionHandler, null, title, accountId, assetId, assetCrn, accountCrn, awaitEvidenceOfPlay); Date expiration = trans.getSecond(); Transaction lockerTrans = lockerService.createTransaction(trans.getFirst()); return new Pair<>(lockerTrans, expiration); } private void validateAvailability(TitlePlayability titlePlayability) { switch (titlePlayability.getDecision()) { case PLAYABLE_TITLE: case PLAYABLE_PREVIEW: case PLAYABLE_NDVR_TITLE: //Anything playable is good break; case NOT_AVAILABLE_ON_VOD_SYSTEM: throw new AssetIsNotAvailableException("Asset not available on CCDN."); case NOT_IN_WINDOW: throw new AssetIsNotAvailableException("License window expired on %1$tm/%1$td/%1$tY.", new Date(titlePlayability.getTitle().getLicenseWindow().getEnd())); case NOT_ENTITLED: throw new NotEntitledException("Not entitled to %s.", titlePlayability.getTitle().getService().getServiceCode()); case NOT_ALLOWED_VIA_DL: throw new AssetIsNotAvailableException("Asset is restricted by distribution list."); case NOT_ALLOWED_VIA_MENU_ITEM: throw new AssetIsNotAvailableException("Asset is not available on the VOD menu."); case NOT_ALLOWED_FOR_CREDIT_HOLD: throw new AccountNotInGoodStandingException("Account on credit hold."); case NOT_ALLOWED_FOR_NO_CHARGES_ON_ACCOUNT: throw new AccountNotInGoodStandingException("No charges allowed on account"); case NOT_ALLOWED_FOR_INVALID_HIBIT: throw new AccountNotInGoodStandingException("Account has invalid hibit."); case NOT_ALLOWED_REQUIRES_HDMI: throw new AssetIsNotAvailableException("Asset requires HDMI."); } } }