/*
* Copyright 1998-2016 Linux.org.ru
* Licensed 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 ru.org.linux.user;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ru.org.linux.util.StringUtil;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class UserEventDao {
private static final String QUERY_ALL_REPLIES_FOR_USER =
"SELECT user_events.id, event_date, " +
" topics.title as subj, " +
" topics.id as msgid, " +
" comments.id AS cid, " +
" comments.userid AS cAuthor, " +
" topics.userid AS tAuthor, " +
" unread, " +
" groupid, comments.deleted," +
" type, user_events.message as ev_msg" +
" FROM user_events INNER JOIN topics ON (topics.id = message_id) " +
" LEFT JOIN comments ON (comments.id=comment_id) " +
" WHERE user_events.userid = ? " +
" %s " +
" ORDER BY id DESC LIMIT ?" +
" OFFSET ?";
private static final String QUERY_REPLIES_FOR_USER_WIHOUT_PRIVATE =
"SELECT user_events.id, event_date, " +
" topics.title as subj, " +
" topics.id as msgid, " +
" comments.id AS cid, " +
" comments.userid AS cAuthor, " +
" topics.userid AS tAuthor, " +
" unread, " +
" groupid, comments.deleted," +
" type, user_events.message as ev_msg" +
" FROM user_events INNER JOIN topics ON (topics.id = message_id) " +
" LEFT JOIN comments ON (comments.id=comment_id) " +
" WHERE user_events.userid = ? " +
" AND NOT private " +
" ORDER BY id DESC LIMIT ?" +
" OFFSET ?";
private SimpleJdbcInsert insert;
private SimpleJdbcInsert insertTopicUsersNotified;
private JdbcTemplate jdbcTemplate;
private NamedParameterJdbcTemplate namedJdbcTemplate;
@Autowired
public void setDataSource(DataSource ds) {
insert = new SimpleJdbcInsert(ds);
insert.setTableName("user_events");
insert.usingColumns("userid", "type", "private", "message_id", "comment_id", "message");
insertTopicUsersNotified = new SimpleJdbcInsert(ds);
insertTopicUsersNotified.setTableName("topic_users_notified");
insertTopicUsersNotified.usingColumns("topic", "userid");
jdbcTemplate = new JdbcTemplate(ds);
namedJdbcTemplate = new NamedParameterJdbcTemplate(ds);
}
/**
* Добавление уведомления
*
* @param eventType тип уведомления
* @param userId идентификационный номер пользователя
* @param isPrivate приватное ли уведомление
* @param topicId идентификационный номер топика (null если нет)
* @param commentId идентификационный номер комментария (null если нет)
* @param message дополнительное сообщение уведомления (null если нет)
*/
public void addEvent(
String eventType,
int userId,
boolean isPrivate,
Integer topicId,
Integer commentId,
String message
) {
Map<String, Object> params = new HashMap<>();
params.put("userid", userId);
params.put("type", eventType);
params.put("private", isPrivate);
if (topicId != null) {
params.put("message_id", topicId);
}
if (commentId != null) {
params.put("comment_id", commentId);
}
if (message != null) {
params.put("message", message);
}
insert.execute(params);
}
public void insertTopicNotification(final int topicId, Iterable<Integer> userIds) {
@SuppressWarnings("unchecked") Map<String, Object>[] batch = Iterables.toArray(
Iterables.transform(
userIds,
new Function<Integer, Map<String, Object>>() {
@Nullable
@Override
public Map<String, Object> apply(Integer userId) {
return ImmutableMap.<String, Object>of("topic", topicId, "userid", userId);
}
}
), Map.class);
insertTopicUsersNotified.executeBatch(batch);
}
public List<Integer> getNotifiedUsers(int topicId) {
return jdbcTemplate.queryForList("SELECT userid FROM topic_users_notified WHERE topic=?", Integer.class, topicId);
}
/**
* Сброс уведомлений.
*
* @param userId идентификационный номер пользователь которому сбрасываем
* @param topId сбрасываем уведомления с идентификатором не больше этого
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void resetUnreadReplies(int userId, int topId) {
jdbcTemplate.update("UPDATE user_events SET unread=false WHERE userid=? AND unread AND id<=?", userId, topId);
recalcEventCount(ImmutableList.of(userId));
}
public void recalcEventCount(Collection<Integer> userids) {
if (userids.isEmpty()) {
return;
}
namedJdbcTemplate.update(
"UPDATE users SET unread_events = (SELECT count(*) FROM user_events WHERE unread AND userid=users.id) WHERE users.id IN (:list)",
ImmutableMap.of("list", userids)
);
}
/**
* Получение списка первых 20 идентификационных номеров пользователей,
* количество уведомлений которых превышает максимально допустимое значение.
*
* @param maxEventsPerUser максимальное количество уведомлений для одного пользователя
* @return список идентификационных номеров пользователей
*/
public List<Integer> getUserIdListByOldEvents(int maxEventsPerUser) {
return jdbcTemplate.queryForList(
"select userid from user_events group by userid having count(user_events.id) > ? order by count(user_events.id) DESC limit 20",
Integer.class,
maxEventsPerUser
);
}
/**
* Очистка старых уведомлений пользователя.
*
* @param userId идентификационный номер пользователя
* @param maxEventsPerUser максимальное количество уведомлений для одного пользователя
*/
public void cleanupOldEvents(int userId, int maxEventsPerUser) {
jdbcTemplate.update(
"DELETE FROM user_events WHERE user_events.id IN (SELECT id FROM user_events WHERE userid=? ORDER BY event_date DESC OFFSET ?)",
userId,
maxEventsPerUser
);
}
/**
* Получить список уведомлений для пользователя.
*
* @param userId идентификационный номер пользователя
* @param showPrivate включать ли приватные
* @param topics кол-во уведомлений
* @param offset сдвиг относительно начала
* @param eventFilterType тип уведомлений
* @return список уведомлений
*/
public List<UserEvent> getRepliesForUser(int userId, boolean showPrivate, int topics, int offset,
String eventFilterType) {
String queryString;
if (showPrivate) {
String queryPart = "";
if (eventFilterType != null) {
queryPart = " AND type = '" + eventFilterType + "' ";
}
queryString = String.format(QUERY_ALL_REPLIES_FOR_USER, queryPart);
} else {
queryString = QUERY_REPLIES_FOR_USER_WIHOUT_PRIVATE;
}
return jdbcTemplate.query(queryString, (resultSet, i) -> {
String subj = StringUtil.makeTitle(resultSet.getString("subj"));
Timestamp eventDate = resultSet.getTimestamp("event_date");
int cid = resultSet.getInt("cid");
int cAuthor;
if (!resultSet.wasNull()) {
cAuthor = resultSet.getInt("cAuthor");
} else {
cAuthor = 0;
}
int groupId = resultSet.getInt("groupid");
int msgid = resultSet.getInt("msgid");
UserEventFilterEnum type = UserEventFilterEnum.valueOfByType(resultSet.getString("type"));
String eventMessage = resultSet.getString("ev_msg");
boolean unread = resultSet.getBoolean("unread");
return new UserEvent(cid, cAuthor,
groupId, subj, msgid, type, eventMessage, eventDate, unread, resultSet.getInt("tAuthor"), resultSet.getInt("id"));
}, userId, topics, offset);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public List<Integer> deleteTopicEvents(Collection<Integer> topics) {
if (topics.isEmpty()) {
return ImmutableList.of();
}
List<Integer> affectedUsers = namedJdbcTemplate.queryForList("SELECT DISTINCT (userid) FROM user_events " +
"WHERE message_id IN (:list) AND type IN ('TAG', 'REF', 'REPLY', 'WATCH')",
ImmutableMap.of("list", topics),
Integer.class);
namedJdbcTemplate.update(
"DELETE FROM user_events WHERE message_id IN (:list) AND type IN ('TAG', 'REF', 'REPLY', 'WATCH')",
ImmutableMap.of("list", topics)
);
return affectedUsers;
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public List<Integer> deleteCommentEvents(Collection<Integer> comments) {
if (comments.isEmpty()) {
return ImmutableList.of();
}
List<Integer> affectedUsers = namedJdbcTemplate.queryForList("SELECT DISTINCT (userid) FROM user_events " +
"WHERE comment_id IN (:list) AND type in ('REPLY', 'WATCH', 'REF')",
ImmutableMap.of("list", comments),
Integer.class);
namedJdbcTemplate.update(
"DELETE FROM user_events WHERE comment_id IN (:list) AND type in ('REPLY', 'WATCH', 'REF')",
ImmutableMap.of("list", comments)
);
return affectedUsers;
}
}