| /* |
| * 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.datalab.backendapi.dao; |
| |
| import com.epam.datalab.backendapi.domain.AuditDTO; |
| import com.epam.datalab.backendapi.domain.AuditPaginationDTO; |
| import com.epam.datalab.exceptions.DatalabException; |
| import com.mongodb.client.model.Facet; |
| import com.mongodb.client.model.Filters; |
| import com.mongodb.client.model.Sorts; |
| import org.apache.commons.collections4.CollectionUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.bson.Document; |
| import org.bson.conversions.Bson; |
| |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.time.Instant; |
| import java.time.ZoneOffset; |
| import java.time.temporal.ChronoUnit; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TimeZone; |
| import java.util.stream.Collectors; |
| import java.util.stream.StreamSupport; |
| |
| import static com.epam.datalab.backendapi.dao.ComputationalDAO.PROJECT; |
| import static com.mongodb.client.model.Aggregates.count; |
| import static com.mongodb.client.model.Aggregates.facet; |
| import static com.mongodb.client.model.Aggregates.group; |
| import static com.mongodb.client.model.Aggregates.limit; |
| import static com.mongodb.client.model.Aggregates.match; |
| import static com.mongodb.client.model.Aggregates.skip; |
| import static com.mongodb.client.model.Aggregates.sort; |
| import static com.mongodb.client.model.Filters.gte; |
| import static com.mongodb.client.model.Filters.in; |
| import static com.mongodb.client.model.Filters.lte; |
| |
| public class AuditDAOImpl extends BaseDAO implements AuditDAO { |
| private static final String AUDIT_COLLECTION = "audit"; |
| private static final String RESOURCE_NAME_FIELD = "resourceName"; |
| private static final String RESOURCE_TYPE_FIELD = "type"; |
| private static final String TIMESTAMP_FIELD = "timestamp"; |
| private static final String COUNT_FIELD = "count"; |
| private static final String AUDIT_FACET = "auditFacet"; |
| private static final String TOTAL_COUNT_FACET = "totalCountFacet"; |
| private static final String RESOURCE_NAME_FACET = "resourceNameFacet"; |
| private static final String USER_FACET = "userFacet"; |
| private static final String PROJECT_FACET = "projectFacet"; |
| private static final String RESOURCE_TYPE_FACET = "typeFacet"; |
| |
| @Override |
| public void save(AuditDTO audit) { |
| insertOne(AUDIT_COLLECTION, audit); |
| } |
| |
| @Override |
| public List<AuditPaginationDTO> getAudit(List<String> users, List<String> projects, List<String> resourceNames, List<String> resourceTypes, String dateStart, String dateEnd, |
| int pageNumber, int pageSize) { |
| List<Bson> valuesPipeline = new ArrayList<>(); |
| List<Bson> countPipeline = new ArrayList<>(); |
| List<Bson> matchCriteria = matchCriteria(users, projects, resourceNames, resourceTypes, dateStart, dateEnd); |
| if (!matchCriteria.isEmpty()) { |
| Bson match = match(Filters.and(matchCriteria)); |
| valuesPipeline.add(match); |
| countPipeline.add(match); |
| } |
| countPipeline.add(count()); |
| valuesPipeline.add(sortCriteria()); |
| valuesPipeline.addAll(Arrays.asList(skip(pageSize * (pageNumber - 1)), limit(pageSize))); |
| |
| List<Bson> userFilter = Collections.singletonList(group(getGroupingFields(USER))); |
| List<Bson> projectFilter = Collections.singletonList(group(getGroupingFields(PROJECT))); |
| List<Bson> resourceNameFilter = Collections.singletonList(group(getGroupingFields(RESOURCE_NAME_FIELD))); |
| List<Bson> resourceTypeFilter = Collections.singletonList(group(getGroupingFields(RESOURCE_TYPE_FIELD))); |
| |
| List<Bson> facets = Collections.singletonList(facet(new Facet(AUDIT_FACET, valuesPipeline), new Facet(TOTAL_COUNT_FACET, countPipeline), |
| new Facet(RESOURCE_NAME_FACET, resourceNameFilter), new Facet(USER_FACET, userFilter), new Facet(PROJECT_FACET, projectFilter), |
| new Facet(RESOURCE_TYPE_FACET, resourceTypeFilter))); |
| return StreamSupport.stream(aggregate(AUDIT_COLLECTION, facets).spliterator(), false) |
| .map(this::toAuditPaginationDTO) |
| .collect(Collectors.toList()); |
| } |
| |
| private List<Bson> matchCriteria(List<String> users, List<String> projects, List<String> resourceNames, List<String> resourceTypes, String dateStart, String dateEnd) { |
| List<Bson> searchCriteria = new ArrayList<>(); |
| inCriteria(searchCriteria, users, USER); |
| inCriteria(searchCriteria, projects, PROJECT); |
| inCriteria(searchCriteria, resourceNames, RESOURCE_NAME_FIELD); |
| inCriteria(searchCriteria, resourceTypes, RESOURCE_TYPE_FIELD); |
| if (StringUtils.isNotEmpty(dateStart)) { |
| Instant from = getInstant(dateStart); |
| Date date = new Date(from.toEpochMilli()); |
| searchCriteria.add(gte(TIMESTAMP_FIELD, date)); |
| } |
| if (StringUtils.isNotEmpty(dateEnd)) { |
| Instant to = getInstant(dateEnd).plus(1, ChronoUnit.DAYS); |
| Date date = new Date(to.toEpochMilli()); |
| searchCriteria.add(lte(TIMESTAMP_FIELD, date)); |
| } |
| return searchCriteria; |
| } |
| |
| private Bson sortCriteria() { |
| return sort(Sorts.descending(TIMESTAMP_FIELD)); |
| } |
| |
| private AuditPaginationDTO toAuditPaginationDTO(Document document) { |
| List<Document> countDocuments = (List<Document>) document.get(TOTAL_COUNT_FACET); |
| final int count = countDocuments.isEmpty() ? 0 : countDocuments.get(0).getInteger(COUNT_FIELD); |
| Set<String> userFilter = getFilter(document, USER_FACET, USER); |
| Set<String> projectFilter = getFilter(document, PROJECT_FACET, PROJECT); |
| Set<String> resourceNameFilter = getFilter(document, RESOURCE_NAME_FACET, RESOURCE_NAME_FIELD); |
| Set<String> resourceTypeFilter = getFilter(document, RESOURCE_TYPE_FACET, RESOURCE_TYPE_FIELD); |
| List<AuditDTO> auditDTOs = (List<AuditDTO>) document.get(AUDIT_FACET); |
| return AuditPaginationDTO.builder() |
| .totalPageCount(count) |
| .audit(auditDTOs) |
| .userFilter(userFilter) |
| .resourceNameFilter(resourceNameFilter) |
| .resourceTypeFilter(resourceTypeFilter) |
| .projectFilter(projectFilter) |
| .build(); |
| } |
| |
| private Set<String> getFilter(Document document, String facet, String field) { |
| return ((List<Document>) document.get(facet)) |
| .stream() |
| .map(d -> (Document) d.get(ID)) |
| .map(d -> d.getString(field)) |
| .collect(Collectors.toSet()); |
| } |
| |
| private Instant getInstant(String dateStart) { |
| Instant from; |
| try { |
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); |
| simpleDateFormat.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); |
| from = simpleDateFormat.parse(dateStart).toInstant(); |
| } catch (ParseException e) { |
| throw new DatalabException(String.format("Cannot parse %s", dateStart), e); |
| } |
| return from; |
| } |
| |
| private void inCriteria(List<Bson> searchCriteria, List<String> users, String user) { |
| if (CollectionUtils.isNotEmpty(users)) { |
| searchCriteria.add(in(user, users)); |
| } |
| } |
| } |