/**
* 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.BoardColumn.BoardColumnLocation;
import io.lavagna.model.*;
import io.lavagna.model.Event.EventType;
import io.lavagna.query.CardDataQuery;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
@Repository
@Transactional(readOnly = true)
public class CardDataRepository {
private static final Logger LOG = LogManager.getLogger();
private final NamedParameterJdbcTemplate jdbc;
private final CardDataQuery queries;
public CardDataRepository(NamedParameterJdbcTemplate jdbc, CardDataQuery queries) {
this.jdbc = jdbc;
this.queries = queries;
}
private static List<String> toStringList(Set<?> s) {
List<String> r = new ArrayList<>(s.size());
for (Object e : s) {
r.add(e.toString());
}
return r;
}
// prepare a {:order, :id, :cardId} list
private static List<SqlParameterSource> prepareOrderParameter(List<Integer> dataIds, int cardId, Integer referenceId) {
LOG.debug("prepareOrderParameter: {card: {}, data count: {}, reference {}}", cardId, dataIds.size(),
referenceId);
List<SqlParameterSource> params = new ArrayList<>(dataIds.size());
for (int i = 0; i < dataIds.size(); i++) {
LOG.debug("prepareOrderParameter FOR: {data: {}, order: {}}", dataIds.get(i), i + 1);
SqlParameterSource p = new MapSqlParameterSource("order", i + 1).addValue("id", dataIds.get(i))
.addValue("cardId", cardId).addValue("referenceId", referenceId);
params.add(p);
}
return params;
}
public CardData getUndeletedDataLightById(int id) {
return queries.getUndeletedDataLightById(id);
}
public CardData getDataLightById(int id) {
return queries.getDataLightById(id);
}
public Map<Integer, String> findDataByIds(Collection<Integer> ids) {
if (ids.isEmpty()) {
return Collections.emptyMap();
}
Map<Integer, String> res = new HashMap<>();
for (CardIdAndContent c : queries.findDataByIds(ids)) {
res.put(c.getId(), c.getContent());
}
return res;
}
public CardDataMetadata findMetadataById(int id) {
return queries.findMetadataById(id);
}
public String findContentWith(int cardId, int referenceId, CardType type, int order) {
List<CardIdAndContent> res = queries.findContentWith(cardId, referenceId, type.toString(), order);
return res.isEmpty() ? null : res.get(0).getContent();
}
public List<CardData> findAllDataLightByCardId(int cardId) {
return queries.findAllLightByCardId(cardId);
}
public List<CardData> findAllDataLightByReferenceId(int referenceId) {
return queries.findAllLightByReferenceId(referenceId);
}
public List<CardData> findAllDataLightByCardIdAndTypes(int cardId, Set<CardType> types) {
return queries.findAllLightByCardIdAndTypes(cardId, toStringList(types));
}
public List<CardData> findAllDataLightByCardIdAndType(int cardId, CardType type) {
return queries.findAllLightByCardIdAndType(cardId, type.toString());
}
public List<CardDataIdAndOrder> findAllByTypes(Set<CardType> types) {
return queries.findAllCardDataIdAndOrderByType(toStringList(types));
}
public List<CardData> findAllDataLightByReferenceIdAndType(int referenceId, CardType type) {
return queries.findAllLightByReferenceIdAndType(referenceId, type.toString());
}
public List<CardDataFull> findAllDataByCardIdAndType(int cardId, CardType type) {
return queries.findAllByCardIdAndType(cardId, type.toString());
}
public List<FileDataLight> findAllFilesByCardId(int cardId) {
return queries.findAllFilesByCardId(cardId);
}
public FileDataLight getUndeletedFileByCardDataId(int cardDataId) {
return queries.getUndeletedFileByCardDataId(cardDataId);
}
public List<CardDataUploadContentInfo> findAllDataUploadContentInfo() {
return queries.findAllDataUploadContentInfo();
}
@Transactional(readOnly = false)
public CardData createData(int cardId, CardType type, String content) {
LOG.debug("createCardData: {card: {}, type: {}, body: {}}", cardId, type, content);
queries.create(cardId, type.toString(), requireNonNull(trimToEmpty(content), "body cannot be empty"));
return queries.findLastCreatedLight();
}
@Transactional(readOnly = false)
public CardData createDataWithReferenceOrder(int cardId, Integer referenceId, CardType type, String content) {
LOG.debug("createDataWithReferenceOrder: {card: {}, reference: {}, type: {}, body: {}}", cardId,
referenceId, type, content);
queries.createWithReferenceOrder(cardId, referenceId, type.toString(),
requireNonNull(trimToEmpty(content), "body cannot be empty"));
return queries.findLastCreatedLight();
}
/**
* Order the action list. Additionally, the ids are filtered.
*
* @param cardId
* @param data
*/
@Transactional(readOnly = false)
public void updateActionListOrder(int cardId, List<Integer> data) {
// we filter out wrong ids
List<Integer> filtered = Utils.filter(data,
queries.findAllCardDataIdsBy(data, cardId, CardType.ACTION_LIST.toString()));
//
SqlParameterSource[] params = new SqlParameterSource[filtered.size()];
for (int i = 0; i < filtered.size(); i++) {
params[i] = new MapSqlParameterSource("order", i + 1).addValue("id", filtered.get(i)).addValue("cardId",
cardId);
}
jdbc.batchUpdate(queries.updateOrder(), params);
}
@Transactional(readOnly = false)
public int updateOrderById(int id, int order) {
return queries.updateOrderById(id, order);
}
/**
* Order the action item inside a action list. Additionally, the ids are filtered.
*
* @param cardId
* @param newReferenceId
* @param newDataOrder
*/
@Transactional(readOnly = false)
public void updateOrderByCardAndReferenceId(int cardId, Integer newReferenceId, List<Integer> newDataOrder) {
List<Integer> filtered = Utils.filter(
newDataOrder,
queries.findAllCardDataIdsBy(newDataOrder, cardId, newReferenceId,
Arrays.asList(CardType.ACTION_CHECKED.toString(), CardType.ACTION_UNCHECKED.toString())));
List<SqlParameterSource> params = prepareOrderParameter(filtered, cardId, newReferenceId);
jdbc.batchUpdate(queries.updateOrderByCardAndReferenceId(),
params.toArray(new SqlParameterSource[params.size()]));
}
@Transactional(readOnly = false)
public int updateReferenceId(int cardId, int dataId, Integer referenceId) {
LOG.debug("updateReferenceId: {card: {}, data: {}, referenceId: {}}", cardId, dataId, referenceId);
return queries.updateReferenceId(referenceId, dataId, cardId);
}
@Transactional(readOnly = false)
public int updateContent(int id, Set<CardType> types, String content) {
return queries.updateContent(requireNonNull(trimToEmpty(content), "body cannot be empty"), id,
toStringList(types));
}
@Transactional(readOnly = false)
public int updateType(int id, Set<CardType> oldTypes, CardType newType) {
LOG.debug("updateType: {item: {}, type: {}}", id, newType);
return queries.updateType(newType.toString(), id, toStringList(oldTypes));
}
@Transactional(readOnly = false)
public int softDelete(int id, Set<CardType> types) {
LOG.debug("softDelete: {id: {}}", id);
return queries.softDelete(id, toStringList(types));
}
@Transactional(readOnly = false)
public int undoSoftDelete(int id, Set<CardType> types) {
LOG.debug("undoSoftDelete: {id: {}}", id);
return queries.undoSoftDelete(id, toStringList(types));
}
@Transactional(readOnly = false)
public int softDeleteOnCascade(int id, Set<CardType> types) {
LOG.debug("softDeleteOnCascade: {id: {}}", id);
return queries.softDeleteOnCascade(id, toStringList(types));
}
@Transactional(readOnly = false)
public int undoSoftDeleteOnCascade(int id, Set<CardType> types, Set<EventType> filteredEvents) {
LOG.debug("undoSoftDeleteOnCascade: {id: {}}", id);
return queries.undoSoftDeleteOnCascade(id, toStringList(types), toStringList(filteredEvents));
}
public List<CardDataCount> findCountsByBoardIdAndLocation(int boardId, BoardColumnLocation location) {
return queries.findCountsByBoardIdAndLocation(boardId, location.toString());
}
public List<CardDataCount> findCountsByCardIds(List<Integer> ids) {
return ids.isEmpty() ? Collections.<CardDataCount> emptyList() : queries.findCountsByCardIds(ids);
}
@Transactional(readOnly = false)
public int addUploadContent(final String digest, final long fileSize, final InputStream content,
final String contentType) {
LobHandler lobHandler = new DefaultLobHandler();
return jdbc.getJdbcOperations().execute(queries.addUploadContent(),
new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
ps.setString(1, digest);
ps.setLong(2, fileSize);
lobCreator.setBlobAsBinaryStream(ps, 3, content, (int) fileSize);
ps.setString(4, contentType);
}
});
}
@Transactional(readOnly = false)
public int createUploadInfo(String digest, String name, String displayName, int cardDataId) {
return queries.mapUploadContent(cardDataId, digest, name, displayName);
}
public boolean fileExists(String digest) {
return queries.findDigest(digest).equals(1);
}
public boolean isFileAvailableByCard(String digest, int cardId) {
return queries.isFileAvailableByCard(cardId, digest).equals(1);
}
public void outputFileContent(String digest, final OutputStream out) throws IOException {
LOG.debug("get file digest : {} ", digest);
SqlParameterSource param = new MapSqlParameterSource("digest", digest);
jdbc.query(queries.fileContent(), param, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
try (InputStream is = rs.getBinaryStream("CONTENT")) {
StreamUtils.copy(is, out);
} catch (IOException e) {
throw new IllegalStateException("Error while copying data", e);
}
}
});
}
}