private List<SubscriptionBaseEvent> toEvents( final DefaultSubscriptionBase defaultSubscriptionBase, final DateTime now, final DateTime ctd, final TimedMigration[] migrationEvents, final CallContext context) { if (ctd == null) { throw new SubscriptionBaseError( String.format("Could not create migration billing event ctd = %s", ctd)); } final List<SubscriptionBaseEvent> events = new ArrayList<SubscriptionBaseEvent>(migrationEvents.length); ApiEventMigrateBilling apiEventMigrateBilling = null; // The first event date after the MIGRATE_ENTITLEMENT event DateTime nextEventDate = null; boolean isCancelledSubscriptionPriorOrAtCTD = false; for (final TimedMigration cur : migrationEvents) { final ApiEventBuilder builder = new ApiEventBuilder() .setSubscriptionId(defaultSubscriptionBase.getId()) .setEventPlan((cur.getPlan() != null) ? cur.getPlan().getName() : null) .setEventPlanPhase((cur.getPhase() != null) ? cur.getPhase().getName() : null) .setEventPriceList(cur.getPriceList()) .setActiveVersion(defaultSubscriptionBase.getActiveVersion()) .setEffectiveDate(cur.getEventTime()) .setProcessedDate(now) .setRequestedDate(now) .setFromDisk(true); if (cur.getEventType() == EventType.PHASE) { nextEventDate = nextEventDate != null && nextEventDate.compareTo(cur.getEventTime()) < 0 ? nextEventDate : cur.getEventTime(); final PhaseEvent nextPhaseEvent = PhaseEventData.createNextPhaseEvent( defaultSubscriptionBase.getId(), defaultSubscriptionBase.getActiveVersion(), cur.getPhase().getName(), now, cur.getEventTime()); events.add(nextPhaseEvent); } else if (cur.getEventType() == EventType.API_USER) { switch (cur.getApiEventType()) { case MIGRATE_ENTITLEMENT: ApiEventMigrateSubscription creationEvent = new ApiEventMigrateSubscription(builder); events.add(creationEvent); break; case CHANGE: nextEventDate = nextEventDate != null && nextEventDate.compareTo(cur.getEventTime()) < 0 ? nextEventDate : cur.getEventTime(); events.add(new ApiEventChange(builder)); break; case CANCEL: isCancelledSubscriptionPriorOrAtCTD = !cur.getEventTime().isAfter(ctd); nextEventDate = nextEventDate != null && nextEventDate.compareTo(cur.getEventTime()) < 0 ? nextEventDate : cur.getEventTime(); events.add(new ApiEventCancel(builder)); break; default: throw new SubscriptionBaseError( String.format("Unexpected type of api migration event %s", cur.getApiEventType())); } } else { throw new SubscriptionBaseError( String.format("Unexpected type of migration event %s", cur.getEventType())); } // create the MIGRATE_BILLING based on the current state of the last event. if (!cur.getEventTime().isAfter(ctd)) { builder.setEffectiveDate(ctd); builder.setUuid(UUID.randomUUID()); apiEventMigrateBilling = new ApiEventMigrateBilling(builder); } } // Always ADD MIGRATE BILLING which is constructed from latest state seen in the stream prior to // CTD if (apiEventMigrateBilling != null && !isCancelledSubscriptionPriorOrAtCTD) { events.add(apiEventMigrateBilling); } Collections.sort( events, new Comparator<SubscriptionBaseEvent>() { int compForApiType( final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2, final ApiEventType type) { ApiEventType apiO1 = null; if (o1.getType() == EventType.API_USER) { apiO1 = ((ApiEvent) o1).getEventType(); } ApiEventType apiO2 = null; if (o2.getType() == EventType.API_USER) { apiO2 = ((ApiEvent) o2).getEventType(); } if (apiO1 != null && apiO1.equals(type)) { return -1; } else if (apiO2 != null && apiO2.equals(type)) { return 1; } else { return 0; } } @Override public int compare(final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2) { int comp = o1.getEffectiveDate().compareTo(o2.getEffectiveDate()); if (comp == 0) { comp = compForApiType(o1, o2, ApiEventType.MIGRATE_ENTITLEMENT); } if (comp == 0) { comp = compForApiType(o1, o2, ApiEventType.MIGRATE_BILLING); } return comp; } }); return events; }
@Override public SubscriptionBase createSubscription( final UUID bundleId, final PlanPhaseSpecifier spec, final DateTime requestedDateWithMs, final InternalCallContext context) throws SubscriptionBaseApiException { try { final String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName(); final DateTime now = clock.getUTCNow(); final DateTime requestedDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now; if (requestedDate.isAfter(now)) { throw new SubscriptionBaseApiException( ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString()); } final DateTime effectiveDate = requestedDate; final Catalog catalog = catalogService.getFullCatalog(); final Plan plan = catalog.findPlan( spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate); final PlanPhase phase = plan.getAllPhases()[0]; if (phase == null) { throw new SubscriptionBaseError( String.format( "No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog", spec.getProductName(), spec.getBillingPeriod().toString(), realPriceList)); } final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context); if (bundle == null) { throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId); } DateTime bundleStartDate = null; final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context); switch (plan.getProduct().getCategory()) { case BASE: if (baseSubscription != null) { if (baseSubscription.getState() == EntitlementState.ACTIVE) { throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId); } } bundleStartDate = requestedDate; break; case ADD_ON: if (baseSubscription == null) { throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId); } if (effectiveDate.isBefore(baseSubscription.getStartDate())) { throw new SubscriptionBaseApiException( ErrorCode.SUB_INVALID_REQUESTED_DATE, effectiveDate.toString(), baseSubscription.getStartDate().toString()); } addonUtils.checkAddonCreationRights(baseSubscription, plan); bundleStartDate = baseSubscription.getStartDate(); break; case STANDALONE: if (baseSubscription != null) { throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId); } // Not really but we don't care, there is no alignment for STANDALONE subscriptions bundleStartDate = requestedDate; break; default: throw new SubscriptionBaseError( String.format( "Can't create subscription of type %s", plan.getProduct().getCategory().toString())); } final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT); return apiService.createPlan( new SubscriptionBuilder() .setId(UUID.randomUUID()) .setBundleId(bundleId) .setCategory(plan.getProduct().getCategory()) .setBundleStartDate(bundleStartDate) .setAlignStartDate(effectiveDate), plan, spec.getPhaseType(), realPriceList, requestedDate, effectiveDate, now, context.toCallContext(tenantId)); } catch (CatalogApiException e) { throw new SubscriptionBaseApiException(e); } }