/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.notifications.internal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.notifications.NotificationException;
import org.xwiki.notifications.NotificationPreference;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryManager;
import org.xwiki.text.StringUtils;
/**
* Generate a query to retrieve notifications events according to the preferences of the user.
*
* @version $Id: 18cd76730034243be7259f3810ebb497c23b0351 $
* @since 9.4RC1
*/
@Component(roles = QueryGenerator.class)
@Singleton
public class QueryGenerator
{
@Inject
private QueryManager queryManager;
@Inject
private ModelBridge modelBridge;
@Inject
private EntityReferenceSerializer<String> serializer;
@Inject
@Named("user")
private ConfigurationSource userPreferencesSource;
/**
* Generate the query.
*
* @param user user interested in the notifications
* @param onlyUnread f only unread events should be returned
* @param endDate do not return events happened after this date
* @param blackList list of ids of blacklisted events to not return (to not get already known events again)
* @return the query to execute
*
* @throws NotificationException if error happens
* @throws QueryException if error happens
*/
public Query generateQuery(DocumentReference user, boolean onlyUnread, Date endDate, List<String> blackList)
throws NotificationException, QueryException
{
// TODO: create a role so extensions can inject their own complex query parts
// TODO: create unit tests for all use-cases
// TODO: idea: handle the items of the watchlist too
// First: get the preferences of the given user
List<NotificationPreference> preferences = modelBridge.getNotificationsPreferences(user);
// Then: generate the HQL query
StringBuilder hql = new StringBuilder();
hql.append("where event.date >= :startDate AND event.user <> :user AND (");
List<String> types = handleEventTypes(hql, preferences);
List<String> apps = handleApplications(hql, preferences, types);
// No notification is returned if nothing is saved in the user settings
// TODO: handle some defaults preferences that can be set in the administration
if (preferences.isEmpty() || (types.isEmpty() && apps.isEmpty())) {
return null;
}
hql.append(")");
handleBlackList(blackList, hql);
handleEndDate(endDate, hql);
handleHiddenEvents(hql);
handleEventStatus(onlyUnread, hql);
handleOrder(hql);
// The, generate the query
Query query = queryManager.createQuery(hql.toString(), Query.HQL);
// Bind values
query.bindValue("startDate", modelBridge.getUserStartDate(user));
query.bindValue("user", serializer.serialize(user));
handleEventTypes(types, query);
handleApplications(apps, query);
handleBlackList(blackList, query);
handleEndDate(endDate, query);
// Return the query
return query;
}
private void handleEndDate(Date endDate, Query query)
{
if (endDate != null) {
query.bindValue("endDate", endDate);
}
}
private void handleBlackList(List<String> blackList, Query query)
{
if (blackList != null && !blackList.isEmpty()) {
query.bindValue("blackList", blackList);
}
}
private void handleEndDate(Date endDate, StringBuilder hql)
{
if (endDate != null) {
hql.append(" AND event.date <= :endDate");
}
}
private void handleBlackList(List<String> blackList, StringBuilder hql)
{
if (blackList != null && !blackList.isEmpty()) {
hql.append(" AND event.id NOT IN (:blackList)");
}
}
private void handleOrder(StringBuilder hql)
{
hql.append(" order by event.date DESC");
}
private void handleEventStatus(boolean onlyUnread, StringBuilder hql)
{
if (onlyUnread) {
hql.append(" AND (event not in (select status.activityEvent from ActivityEventStatusImpl status "
+ "where status.activityEvent = event and status.entityId = :user and status.read = true))");
}
}
private void handleHiddenEvents(StringBuilder hql)
{
// Don't show hidden events unless the user want to display hidden pages
if (userPreferencesSource.getProperty("displayHiddenDocuments", 0) == 0) {
hql.append(" AND event.hidden <> true");
}
}
private void handleApplications(List<String> apps, Query query)
{
if (!apps.isEmpty()) {
query.bindValue("apps", apps);
}
}
private void handleEventTypes(List<String> types, Query query)
{
if (!types.isEmpty()) {
query.bindValue("types", types);
}
}
private List<String> handleApplications(StringBuilder hql, List<NotificationPreference> preferences,
List<String> types)
{
List<String> apps = new ArrayList<>();
for (NotificationPreference preference : preferences) {
if (preference.isNotificationEnabled() && StringUtils.isNotBlank(preference.getApplicationId())) {
apps.add(preference.getApplicationId());
}
}
if (!apps.isEmpty()) {
hql.append((types.isEmpty() ? "" : " OR ") + "event.application IN (:apps)");
}
return apps;
}
private List<String> handleEventTypes(StringBuilder hql, List<NotificationPreference> preferences)
{
List<String> types = new ArrayList<>();
for (NotificationPreference preference : preferences) {
if (preference.isNotificationEnabled() && StringUtils.isNotBlank(preference.getEventType())) {
types.add(preference.getEventType());
}
}
if (!types.isEmpty()) {
hql.append("event.type IN (:types)");
}
return types;
}
}