/*
* Copyright (C) 2008-2017, Juick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.juick.service;
import com.juick.Tag;
import com.juick.User;
import com.juick.server.helpers.TagStats;
import com.juick.util.StreamUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* Created by aalexeev on 11/13/16.
*/
@Repository
public class TagServiceImpl extends BaseJdbcService implements TagService {
@Transactional(readOnly = true)
@Override
public com.juick.Tag getTag(final int tid) {
List list = getJdbcTemplate().query(
"SELECT synonym_id,name FROM tags WHERE tag_id=?",
(rs, num) -> {
Tag ret = new Tag(rs.getString(2));
ret.TID = tid;
ret.SynonymID = rs.getInt(1);
return ret;
},
tid);
return list.isEmpty() ?
null : list.get(0);
}
@Transactional
@Override
public com.juick.Tag getTag(final String tag, final boolean autoCreate) {
if (StringUtils.isBlank(tag))
return null;
List list = getJdbcTemplate().query(
"SELECT tag_id, synonym_id, name FROM tags WHERE name = ?",
(rs, rowNum) -> {
Tag ret1 = new Tag(rs.getString(3));
ret1.TID = rs.getInt(1);
ret1.SynonymID = rs.getInt(2);
return ret1;
},
tag);
Tag ret = list.isEmpty() ?
null : list.get(0);
if (ret == null && autoCreate) {
ret = new com.juick.Tag(tag);
ret.TID = createTag(tag);
}
return ret;
}
@Override
public List getTags(Stream tags, final boolean autoCreate) {
return tags.filter(StringUtils::isNotBlank).map(tag -> getTag(tag, autoCreate)).filter(Objects::nonNull).distinct()
.collect(Collectors.toList());
}
@Transactional(readOnly = true)
@Override
public boolean getTagNoIndex(final int tagId) {
List list = getJdbcTemplate().queryForList(
"SELECT noindex FROM tags WHERE tag_id=?", Integer.class, tagId);
return !list.isEmpty() && list.get(0) == 1;
}
@Transactional
@Override
public int createTag(final String name) {
KeyHolder holder = new GeneratedKeyHolder();
getJdbcTemplate().update(
con -> {
PreparedStatement stmt = con.prepareStatement(
"INSERT INTO tags(name) VALUES (?)",
Statement.RETURN_GENERATED_KEYS);
stmt.setString(1, name);
return stmt;
},
holder);
return holder.getKey().intValue();
}
private class TagStatsMapper implements RowMapper {
@Override
public TagStats mapRow(ResultSet rs, int rowNum) throws SQLException {
Tag t = new Tag(rs.getString(1));
TagStats s = new TagStats();
s.setTag(t);
s.setUsageCount(rs.getInt(2));
return s;
}
}
@Transactional(readOnly = true)
@Override
public List getUserTagStats(final int uid) {
return getJdbcTemplate().query(
"SELECT tags.name,COUNT(messages.message_id) " +
"FROM (messages INNER JOIN messages_tags ON (messages.user_id=? " +
"AND messages.message_id=messages_tags.message_id)) " +
"INNER JOIN tags ON messages_tags.tag_id=tags.tag_id GROUP BY tags.tag_id ORDER BY tags.name ASC",
new TagStatsMapper(),
uid);
}
@Transactional(readOnly = true)
@Override
public List getUserBLTags(final int uid) {
return getJdbcTemplate().queryForList(
"SELECT tags.name FROM tags INNER JOIN bl_tags " +
"ON (bl_tags.user_id = ? AND bl_tags.tag_id = tags.tag_id) ORDER BY tags.name",
String.class, uid);
}
@Transactional(readOnly = true)
@Override
public List getPopularTags() {
return getJdbcTemplate().queryForList(
"select name from tags where noindex=0 order by stat_messages desc limit 20", String.class);
}
@Transactional(readOnly = true)
@Override
public List getTagStats() {
return getJdbcTemplate().query(
"SELECT tags.name,COUNT(DISTINCT messages.user_id) AS cnt " +
"FROM (messages INNER JOIN messages_tags ON (messages.ts>TIMESTAMPADD(DAY,-3,NOW()) " +
"AND messages.message_id=messages_tags.message_id)) " +
"INNER JOIN tags ON messages_tags.tag_id=tags.tag_id " +
"WHERE tags.tag_id NOT IN (SELECT tag_id FROM tags_ignore) " +
"GROUP BY tags.tag_id ORDER BY cnt DESC LIMIT 20", new TagStatsMapper());
}
@Transactional
@Override
public List updateTags(final int mid, final Collection newTags) {
List currentTags = getMessageTags(mid).stream()
.map(TagStats::getTag).collect(Collectors.toList());
if (CollectionUtils.isEmpty(newTags))
return currentTags;
List idsForDelete = newTags.stream()
.filter(currentTags::contains)
.map(tag -> tag.TID)
.collect(Collectors.toList());
if (newTags.size() - idsForDelete.size() >= 5) {
return currentTags;
}
if (!idsForDelete.isEmpty())
getNamedParameterJdbcTemplate().update(
"DELETE FROM messages_tags WHERE message_id = :mid AND tag_id in (:ids)",
new MapSqlParameterSource().addValue("ids", idsForDelete).addValue("mid", mid));
newTags.stream().filter(t -> !currentTags.contains(t))
.forEach(t -> getJdbcTemplate().update("INSERT INTO messages_tags(message_id,tag_id) VALUES (?,?)", mid, t.TID));
List result = getMessageTags(mid).stream()
.map(TagStats::getTag).collect(Collectors.toList());
jdbcTemplate.update("UPDATE messages_txt SET tags=? WHERE message_id=?", result.stream()
.map(Tag::getName).collect(Collectors.joining(" ")), mid);
return result;
}
@Override
public List fromString(final String txt) {
String firstLine = txt.split("\\n", 2)[0];
return StreamUtils.takeWhile(Arrays.stream(firstLine.split("\\ ")),
t -> !t.equals("*") && t.startsWith("*"))
.map(t -> getTag(t.substring(1), true))
.distinct().collect(Collectors.toList());
}
@Transactional(readOnly = true)
@Override
public List getMessageTags(final int mid) {
return getJdbcTemplate().query(
"SELECT tags.tag_id,synonym_id,name,stat_messages FROM tags " +
"INNER JOIN messages_tags ON (messages_tags.message_id = ? AND messages_tags.tag_id = tags.tag_id)",
(rs, num) -> {
com.juick.Tag t = new com.juick.Tag(rs.getString(3));
t.TID = rs.getInt(1);
t.SynonymID = rs.getInt(2);
TagStats s = new TagStats();
s.setTag(t);
s.setUsageCount(rs.getInt(4));
return s;
}, mid);
}
@Transactional(readOnly = true)
@Override
public List getMessageTagsIDs(final int mid) {
return getJdbcTemplate().queryForList(
"SELECT tag_id FROM messages_tags WHERE message_id = ?",
Integer.class, mid);
}
@Override
public boolean blacklistTag(User user, Tag tag) {
int rowcount = getNamedParameterJdbcTemplate().update("DELETE FROM bl_tags WHERE tag_id = :tid AND user_id = :uid",
new MapSqlParameterSource().addValue("tid", tag.TID).addValue("uid", user.getUid()));
return rowcount <= 0 && getNamedParameterJdbcTemplate()
.update("INSERT INTO bl_tags(user_id, tag_id) VALUES(:uid,:tid)",
new MapSqlParameterSource().addValue("tid", tag.TID)
.addValue("uid", user.getUid())) > 0;
}
@Transactional(readOnly = true)
@Override
public boolean isInBL(User user, Tag tag) {
List list = getJdbcTemplate().queryForList(
"SELECT 1 FROM bl_tags WHERE user_id = ? AND tag_id = ?",
Integer.class, user.getUid(), tag.TID);
return !list.isEmpty() && list.get(0) == 1;
}
@Transactional(readOnly = true)
@Override
public boolean isSubscribed(User user, Tag tag) {
List list = getJdbcTemplate().queryForList(
"SELECT 1 FROM subscr_tags WHERE suser_id = ? AND tag_id = ?",
Integer.class, user.getUid(), tag.TID);
return !list.isEmpty() && list.get(0) == 1;
}
}