/**
* This file is part of lavagna.
*
* lavagna is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lavagna is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lavagna. If not, see <http://www.gnu.org/licenses/>.
*/
package io.lavagna.service;
import io.lavagna.model.CardFull;
import io.lavagna.model.CardLabel;
import io.lavagna.model.CardLabel.LabelDomain;
import io.lavagna.model.CardLabelValue.LabelValue;
import io.lavagna.model.LabelAndValue;
import io.lavagna.model.User;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.Map.Entry;
import static io.lavagna.common.Constants.*;
@Service
@Transactional(readOnly = false)
public class BulkOperationService {
private final CardRepository cardRepository;
private final CardLabelRepository cardLabelRepository;
private final ProjectService projectService;
private final LabelService labelService;
public BulkOperationService(CardRepository cardRepository, CardLabelRepository cardLabelRepository,
LabelService labelService, ProjectService projectService) {
this.cardRepository = cardRepository;
this.cardLabelRepository = cardLabelRepository;
this.labelService = labelService;
this.projectService = projectService;
}
public List<Integer> assign(String projectShortName, List<Integer> cardIds, LabelValue value, User user) {
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
int labelId = findBy(projectShortName, SYSTEM_LABEL_ASSIGNED, LabelDomain.SYSTEM).getId();
// we remove the cards that have _already_ the user assigned
Collection<Integer> alreadyWithUserAssigned = keepCardWithMatching(filteredCardIds,
new FilterByLabelIdAndLabelValue(labelId, value)).keySet();
filteredCardIds.removeAll(alreadyWithUserAssigned);
//
labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
return filteredCardIds;
}
public List<Integer> removeAssign(String projectShortName, List<Integer> cardIds, LabelValue value, User user) {
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
int labelId = findBy(projectShortName, SYSTEM_LABEL_ASSIGNED, LabelDomain.SYSTEM).getId();
List<Integer> removedIds = new ArrayList<>();
for (LabelAndValue lv : flatten(keepCardWithMatching(filteredCardIds,
new FilterByLabelIdAndLabelValue(labelId, value)).values())) {
labelService.removeLabelValue(lv.labelValue(), user, new Date());
removedIds.add(lv.getLabelValueCardId());
}
return removedIds;
}
public List<Integer> reAssign(String projectShortName, List<Integer> cardIds, LabelValue value, User user) {
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
int labelId = findBy(projectShortName, SYSTEM_LABEL_ASSIGNED, LabelDomain.SYSTEM).getId();
// remove all assigned labels
for (LabelAndValue lv : flatten(keepCardWithMatching(filteredCardIds, new FilterByLabelId(labelId)).values())) {
labelService.removeLabelValue(lv.labelValue(), user, new Date());
}
//
labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
return filteredCardIds;
}
public ImmutablePair<List<Integer>, List<Integer>> setDueDate(String projectShortName, List<Integer> cardIds,
LabelValue value, User user) {
return addLabelOrUpdate(projectShortName, cardIds, value, user, SYSTEM_LABEL_DUE_DATE, LabelDomain.SYSTEM);
}
public List<Integer> removeDueDate(String projectShortName, List<Integer> cardIds, User user) {
return removeLabelWithName(projectShortName, cardIds, user, SYSTEM_LABEL_DUE_DATE, LabelDomain.SYSTEM);
}
public ImmutablePair<List<Integer>, List<Integer>> setMilestone(String projectShortName, List<Integer> cardIds,
LabelValue value, User user) {
return addLabelOrUpdate(projectShortName, cardIds, value, user, SYSTEM_LABEL_MILESTONE, LabelDomain.SYSTEM);
}
public List<Integer> removeMilestone(String projectShortName, List<Integer> cardIds, User user) {
return removeLabelWithName(projectShortName, cardIds, user, SYSTEM_LABEL_MILESTONE, LabelDomain.SYSTEM);
}
public List<Integer> watch(String projectShortName, List<Integer> cardIds, User user) {
CardLabel cl = findBy(projectShortName, SYSTEM_LABEL_WATCHED_BY, LabelDomain.SYSTEM);
return addLabel(projectShortName, new LabelValue(user.getId()), cardIds, user, cl);
}
public List<Integer> removeWatch(String projectShortName, List<Integer> cardIds, User user) {
return removeLabelWithNameAndValue(projectShortName, cardIds, user, SYSTEM_LABEL_WATCHED_BY, LabelDomain.SYSTEM, new LabelValue(user.getId()));
}
public List<Integer> removeUserLabel(String projectShortName, int labelId, LabelValue value, List<Integer> cardIds, User user) {
CardLabel cl = cardLabelRepository.findLabelById(labelId);
Validate.isTrue(cl.getDomain() == LabelDomain.USER);
int projectId = projectService.findIdByShortName(projectShortName);
Validate.isTrue(cl.getProjectId() == projectId);
return value == null ? removeLabelWithName(projectShortName, cardIds, user, cl.getName(), LabelDomain.USER) :
removeLabelWithNameAndValue(projectShortName, cardIds, user, cl.getName(), LabelDomain.USER, value);
}
public List<Integer> addUserLabel(String projectShortName, Integer labelId, LabelValue value, List<Integer> cardIds,
User user) {
CardLabel cl = cardLabelRepository.findLabelById(labelId);
Validate.isTrue(cl.getDomain() == LabelDomain.USER);
return addLabel(projectShortName, value, cardIds, user, cl);
}
private List<Integer> addLabel(String projectShortName, LabelValue value, List<Integer> cardIds, User user, CardLabel cl) {
int labelId = cl.getId();
int projectId = projectService.findIdByShortName(projectShortName);
Validate.isTrue(cl.getProjectId() == projectId);
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
Collection<Integer> alreadyWithLabel = keepCardWithMatching(filteredCardIds,
new FilterByLabelIdAndLabelValueAndUniqueness(labelId, value)).keySet();
filteredCardIds.removeAll(alreadyWithLabel);
//
labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
return filteredCardIds;
}
private List<Integer> removeLabelWithName(String projectShortName, List<Integer> cardIds, User user,
String labelName, LabelDomain labelDomain) {
int labelId = findBy(projectShortName, labelName, labelDomain).getId();
return removeMatchingLabel(projectShortName, user, cardIds, new FilterByLabelId(labelId));
}
private List<Integer> removeLabelWithNameAndValue(String projectShortName, List<Integer> cardIds, User user,
String labelName, LabelDomain labelDomain, LabelValue labelValue) {
int labelId = findBy(projectShortName, labelName, labelDomain).getId();
return removeMatchingLabel(projectShortName, user, cardIds, new FilterByLabelIdAndLabelValue(labelId, labelValue));
}
private List<Integer> removeMatchingLabel(String projectShortName, User user, List<Integer> cardIds, FilterLabelAndValue filter) {
List<Integer> affected = new ArrayList<>();
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
for (LabelAndValue lv : flatten(keepCardWithMatching(filteredCardIds, filter).values())) {
labelService.removeLabelValue(lv.labelValue(), user, new Date());
affected.add(lv.getLabelValueCardId());
}
return affected;
}
private ImmutablePair<List<Integer>, List<Integer>> addLabelOrUpdate(String projectShortName,
List<Integer> cardIds, LabelValue value, User user, String labelName, LabelDomain labelDomain) {
List<Integer> filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
int labelId = findBy(projectShortName, labelName, labelDomain).getId();
Map<Integer, List<LabelAndValue>> cardsWithDueDate = keepCardWithMatching(filteredCardIds, new FilterByLabelId(
labelId));
List<Integer> updatedCardIds = new ArrayList<>();
// to update only if the label value has changed
for (LabelAndValue lv : flatten(cardsWithDueDate.values())) {
if (!lv.labelValue().getValue().equals(value)) {
labelService.updateLabelValue(lv.labelValue().newValue(lv.getLabelType(), value), user, new Date());
updatedCardIds.add(lv.getLabelValueCardId());
}
}
// to add
filteredCardIds.removeAll(cardsWithDueDate.keySet());
labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
return ImmutablePair.of(updatedCardIds, filteredCardIds);
}
private Map<Integer, List<LabelAndValue>> keepCardWithMatching(List<Integer> cardIds, FilterLabelAndValue filter) {
Map<Integer, List<LabelAndValue>> res = new HashMap<>();
for (Entry<Integer, List<LabelAndValue>> kv : cardLabelRepository.findCardLabelValuesByCardIds(cardIds)
.entrySet()) {
List<LabelAndValue> matchingLabelIdAndLabelValue = filter.filter(kv.getValue());
if (!matchingLabelIdAndLabelValue.isEmpty()) {
res.put(kv.getKey(), matchingLabelIdAndLabelValue);
}
}
return res;
}
private static class FilterByLabelId implements FilterLabelAndValue {
private final int labelId;
private FilterByLabelId(int labelId) {
this.labelId = labelId;
}
@Override
public List<LabelAndValue> filter(List<LabelAndValue> lvs) {
List<LabelAndValue> matching = new ArrayList<>();
for (LabelAndValue lv : lvs) {
if (lv.getLabelId() == labelId) {
matching.add(lv);
}
}
return matching;
}
}
/**
* Keep a list of all the cards that already have a label assigned (if it's a unique label) or a label+value
* combination
*/
private static class FilterByLabelIdAndLabelValueAndUniqueness implements FilterLabelAndValue {
private final int labelId;
private final LabelValue value;
private FilterByLabelIdAndLabelValueAndUniqueness(int labelId, LabelValue value) {
this.labelId = labelId;
this.value = value;
}
@Override
public List<LabelAndValue> filter(List<LabelAndValue> lvs) {
List<LabelAndValue> matching = new ArrayList<>();
for (LabelAndValue lv : lvs) {
if (lv.getLabelId() == labelId && (lv.getLabelUnique() || lv.getValue().equals(value))) {
matching.add(lv);
}
}
return matching;
}
}
private static class FilterByLabelIdAndLabelValue implements FilterLabelAndValue {
private final int labelId;
private final LabelValue value;
private FilterByLabelIdAndLabelValue(int labelId, LabelValue value) {
this.labelId = labelId;
this.value = value;
}
@Override
public List<LabelAndValue> filter(List<LabelAndValue> lvs) {
List<LabelAndValue> matching = new ArrayList<>();
for (LabelAndValue lv : lvs) {
if (lv.getLabelId() == labelId && lv.getValue().equals(value)) {
matching.add(lv);
}
}
return matching;
}
}
private interface FilterLabelAndValue {
List<LabelAndValue> filter(List<LabelAndValue> lvs);
}
private static <T> List<T> flatten(Collection<? extends Collection<T>> cc) {
List<T> res = new ArrayList<>();
for (Collection<T> c : cc) {
res.addAll(c);
}
return res;
}
private List<Integer> keepCardIdsInProject(List<Integer> ids, String projectShortName) {
if (ids.isEmpty()) {
return Collections.emptyList();
}
List<Integer> res = new ArrayList<>(ids.size());
for (CardFull cf : cardRepository.findAllByIds(ids)) {
if (projectShortName.equals(cf.getProjectShortName())) {
res.add(cf.getId());
}
}
return res;
}
public int findIdForSystemLabel(String shortName, String name) {
return findBy(shortName, name, LabelDomain.SYSTEM).getId();
}
private CardLabel findBy(String shortName, String name, LabelDomain labelDomain) {
int projectId = projectService.findIdByShortName(shortName);
return cardLabelRepository.findLabelByName(projectId, name, labelDomain);
}
}