blob: 189a282e6698b56438547f1e9171a863e6c1cb83 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.epam.dlab.backendapi.dao;
import com.epam.dlab.backendapi.domain.BillingReportLine;
import com.epam.dlab.backendapi.resources.dto.BillingFilter;
import com.epam.dlab.dto.billing.BillingResourceType;
import com.google.inject.Inject;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.conversions.Bson;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.epam.dlab.backendapi.dao.MongoCollections.BILLING;
import static com.mongodb.client.model.Accumulators.max;
import static com.mongodb.client.model.Accumulators.min;
import static com.mongodb.client.model.Accumulators.sum;
import static com.mongodb.client.model.Aggregates.group;
import static com.mongodb.client.model.Aggregates.match;
import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.gte;
import static com.mongodb.client.model.Filters.in;
import static com.mongodb.client.model.Filters.lte;
import static com.mongodb.client.model.Filters.regex;
import static java.util.Collections.singletonList;
@Slf4j
public class BaseBillingDAO extends BaseDAO implements BillingDAO {
private static final int ONE_HUNDRED = 100;
private static final String COST_FIELD = "$cost";
private static final String TOTAL_FIELD_NAME = "total";
private static final String PROJECT = "project";
private static final String APPLICATION = "application";
private static final String USAGE_DATE = "usageDate";
private static final String USER = "user";
private static final String RESOURCE_TYPE = "resource_type";
private static final String DLAB_ID = "dlabId";
private static final String FROM = "from";
private static final String TO = "to";
private static final String PRODUCT = "product";
private static final String CURRENCY = "currency";
private static final String COST = "cost";
@Inject
protected SettingsDAO settings;
@Inject
private UserSettingsDAO userSettingsDAO;
@Inject
private ProjectDAO projectDAO;
@Override
public Double getTotalCost() {
return aggregateBillingData(singletonList(group(null, sum(TOTAL_FIELD_NAME, COST_FIELD))));
}
@Override
public Double getUserCost(String user) {
final List<Bson> pipeline = Arrays.asList(match(eq(USER, user)),
group(null, sum(TOTAL_FIELD_NAME, COST_FIELD)));
return aggregateBillingData(pipeline);
}
@Override
public Double getProjectCost(String project) {
final List<Bson> pipeline = Arrays.asList(match(eq(PROJECT, project)),
group(null, sum(TOTAL_FIELD_NAME, COST_FIELD)));
return aggregateBillingData(pipeline);
}
@Override
public int getBillingQuoteUsed() {
return toPercentage(() -> settings.getMaxBudget(), getTotalCost());
}
@Override
public int getBillingUserQuoteUsed(String user) {
return toPercentage(() -> userSettingsDAO.getAllowedBudget(user), getUserCost(user));
}
@Override
public boolean isBillingQuoteReached() {
return getBillingQuoteUsed() >= ONE_HUNDRED;
}
@Override
public boolean isUserQuoteReached(String user) {
final Double userCost = getUserCost(user);
return userSettingsDAO.getAllowedBudget(user)
.filter(allowedBudget -> userCost.intValue() != 0 && allowedBudget <= userCost)
.isPresent();
}
@Override
public boolean isProjectQuoteReached(String project) {
final Double projectCost = getProjectCost(project);
return projectDAO.getAllowedBudget(project)
.filter(allowedBudget -> projectCost.intValue() != 0 && allowedBudget <= projectCost)
.isPresent();
}
@Override
public List<BillingReportLine> findBillingData(List<String> dlabIds) {
return find(BILLING, in(DLAB_ID, dlabIds), BillingReportLine.class);
}
@Override
public int getBillingProjectQuoteUsed(String project) {
return toPercentage(() -> projectDAO.getAllowedBudget(project), getProjectCost(project));
}
public List<BillingReportLine> aggregateBillingData(BillingFilter filter) {
List<Bson> pipeline = new ArrayList<>();
List<Bson> matchCriteria = matchCriteria(filter);
if (!matchCriteria.isEmpty()) {
pipeline.add(Aggregates.match(Filters.and(matchCriteria)));
}
pipeline.add(groupCriteria());
return StreamSupport.stream(getCollection(BILLING).aggregate(pipeline).spliterator(), false)
.map(this::toBillingReport)
.collect(Collectors.toList());
}
@Override
public void deleteByUsageDate(String application, String usageDate) {
deleteMany(BILLING, and(eq(APPLICATION, application), regex(USAGE_DATE, "^" + usageDate)));
}
@Override
public void save(List<BillingReportLine> billingData) {
if (CollectionUtils.isNotEmpty(billingData)) {
insertMany(BILLING, new ArrayList<>(billingData));
}
}
private Integer toPercentage(Supplier<Optional<Integer>> allowedBudget, Double totalCost) {
return allowedBudget.get()
.map(userBudget -> (totalCost * ONE_HUNDRED) / userBudget)
.map(Double::intValue)
.orElse(BigDecimal.ZERO.intValue());
}
private Double aggregateBillingData(List<Bson> pipeline) {
return Optional.ofNullable(aggregate(BILLING, pipeline).first())
.map(d -> d.getDouble(TOTAL_FIELD_NAME))
.orElse(BigDecimal.ZERO.doubleValue());
}
private Bson groupCriteria() {
return group(getGroupingFields(USER, DLAB_ID, RESOURCE_TYPE, PROJECT, PRODUCT, CURRENCY),
sum(COST, "$" + COST),
min(FROM, "$" + FROM),
max(TO, "$" + TO));
}
private List<Bson> matchCriteria(BillingFilter filter) {
List<Bson> searchCriteria = new ArrayList<>();
if (CollectionUtils.isNotEmpty(filter.getUsers())) {
searchCriteria.add(in(USER, filter.getUsers()));
}
if (CollectionUtils.isNotEmpty(filter.getResourceTypes())) {
searchCriteria.add(in(RESOURCE_TYPE, filter.getResourceTypes()));
}
if (StringUtils.isNotEmpty(filter.getDlabId())) {
searchCriteria.add(regex(DLAB_ID, filter.getDlabId(), "i"));
}
if (StringUtils.isNotEmpty(filter.getDateStart())) {
searchCriteria.add(gte(USAGE_DATE, filter.getDateStart()));
}
if (StringUtils.isNotEmpty(filter.getDateEnd())) {
searchCriteria.add(lte(USAGE_DATE, filter.getDateEnd()));
}
if (CollectionUtils.isNotEmpty(filter.getProjects())) {
searchCriteria.add(in(PROJECT, filter.getProjects()));
}
if (CollectionUtils.isNotEmpty(filter.getProducts())) {
searchCriteria.add(in(PRODUCT, filter.getProducts()));
}
return searchCriteria;
}
private BillingReportLine toBillingReport(Document d) {
Document id = (Document) d.get("_id");
return BillingReportLine.builder()
.dlabId(id.getString(DLAB_ID))
.project(id.getString(PROJECT))
.user(id.getString(USER))
.product(id.getString(PRODUCT))
.resourceType(BillingResourceType.valueOf(id.getString(RESOURCE_TYPE)))
.usageDateFrom(d.getDate(FROM).toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
.usageDateTo(d.getDate(TO).toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
.cost(d.getDouble(COST))
.currency(id.getString(CURRENCY))
.build();
}
}