Skip to content

Commit

Permalink
User saved selections. (#1866)
Browse files Browse the repository at this point in the history
* User saved selections.

* User saved selections / tests.

* User saved selections / db creation & migration.

* User saved selections / Add watchlist mail notification.

* User saved selection / Removal when user or records deleted.

* User saved selection / Configure logging.

* User saved selection / Improve test.
  • Loading branch information
fxprunayre committed Feb 23, 2017
1 parent 9275764 commit 10a280d
Show file tree
Hide file tree
Showing 71 changed files with 2,793 additions and 191 deletions.
1 change: 1 addition & 0 deletions core/src/main/java/org/fao/geonet/constants/Geonet.java
Expand Up @@ -79,6 +79,7 @@ public final class Geonet {
public static final String CSW_HARVEST = CSW + ".harvest";
public static final String SRU = GEONETWORK + ".sru";
public static final String SRU_SEARCH = SRU + ".search";
public static final String USER_WATCHLIST = GEONETWORK + ".userwatchlist";
public static final String OAI = GEONETWORK + ".oai";
public static final String OAI_HARVESTER = OAI + ".provider";
// keys for logging search log
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/fao/geonet/kernel/DataManager.java
Expand Up @@ -108,6 +108,7 @@
import org.fao.geonet.repository.Updater;
import org.fao.geonet.repository.UserGroupRepository;
import org.fao.geonet.repository.UserRepository;
import org.fao.geonet.repository.UserSavedSelectionRepository;
import org.fao.geonet.repository.specification.MetadataFileUploadSpecs;
import org.fao.geonet.repository.specification.MetadataSpecs;
import org.fao.geonet.repository.specification.MetadataStatusSpecs;
Expand Down Expand Up @@ -2084,6 +2085,7 @@ private void deleteMetadataFromDB(ServiceContext context, String id) throws Exce
getApplicationContext().getBean(MetadataRatingByIpRepository.class).deleteAllById_MetadataId(intId);
getBean(MetadataValidationRepository.class).deleteAllById_MetadataId(intId);
getApplicationContext().getBean(MetadataStatusRepository.class).deleteAllById_MetadataId(intId);
getApplicationContext().getBean(UserSavedSelectionRepository.class).deleteAllByUuid(getMetadataUuid(id));

// Logical delete for metadata file uploads
PathSpec<MetadataFileUpload, String> deletedDatePathSpec = new PathSpec<MetadataFileUpload, String>() {
Expand Down
240 changes: 240 additions & 0 deletions core/src/main/java/org/fao/geonet/kernel/WatchListNotifier.java
@@ -0,0 +1,240 @@
//=============================================================================
//=== Copyright (C) 2001-2014 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
//=== This program 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 2 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
//=== General Public License for more details.
//===
//=== You should have received a copy of the GNU General Public License
//=== along with this program; if not, write to the Free Software
//=== Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
//===
//=== Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
//=== Rome - Italy. email: geonetwork@osgeo.org
//==============================================================================

package org.fao.geonet.kernel;

import org.apache.commons.lang.StringUtils;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.ISODate;
import org.fao.geonet.domain.Selection;
import org.fao.geonet.domain.User;
import org.fao.geonet.kernel.setting.SettingManager;
import org.fao.geonet.repository.SelectionRepository;
import org.fao.geonet.repository.UserRepository;
import org.fao.geonet.repository.UserSavedSelectionRepository;
import org.fao.geonet.util.MailUtil;
import org.fao.geonet.utils.Log;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

import static org.fao.geonet.kernel.metadata.DefaultStatusActions.compileMessageWithIndexFields;
import static org.fao.geonet.kernel.setting.Settings.SYSTEM_USER_LASTNOTIFICATIONDATE;

/**
* Task checking on a regular basis the list of records
* with changes in user watch list.
*/
public class WatchListNotifier extends QuartzJobBean {

private String lastNotificationDate;
private String nextLastNotificationDate;
private String subject;
private String message;
private String recordMessage;
private String updatedRecordPermalink;
private String language = "eng";
private SettingManager settingManager;
private ApplicationContext appContext;
private UserSavedSelectionRepository userSavedSelectionRepository;
private UserRepository userRepository;

@Value("${usersavedselection.watchlist.searchurl}")
private String permalinkApp = "catalog.search#/search?_uuid={{filter}}";

@Value("${usersavedselection.watchlist.recordurl}")
private String permalinkRecordApp = "api/records/{{index:_uuid}}";

public String getPermalinkApp() {
return permalinkApp;
}

public void setPermalinkApp(String permalinkApp) {
this.permalinkApp = permalinkApp;
}

public String getPermalinkRecordApp() {
return permalinkRecordApp;
}

public void setPermalinkRecordApp(String permalinkRecordApp) {
this.permalinkRecordApp = permalinkRecordApp;
}

public WatchListNotifier() {
}

@Override
protected void executeInternal(JobExecutionContext jobContext) throws JobExecutionException {
appContext = ApplicationContextHolder.get();
settingManager = appContext.getBean(SettingManager.class);

ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages",
new Locale(
language
));

try {
subject = messages.getString("user_watchlist_subject");
message = messages.getString("user_watchlist_message");
recordMessage = messages.getString("user_watchlist_message_record").
replace("{{link}}",
settingManager.getNodeURL() + permalinkRecordApp);
} catch (Exception e) {
}

updatedRecordPermalink = settingManager.getSiteURL(language);

lastNotificationDate = settingManager.getValue(SYSTEM_USER_LASTNOTIFICATIONDATE);
nextLastNotificationDate = new ISODate().toString();

if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
"Last notification date for saved selection was %s.",
lastNotificationDate
));
}

userSavedSelectionRepository = appContext.getBean(UserSavedSelectionRepository.class);
userRepository = appContext.getBean(UserRepository.class);

SelectionRepository selectionRepository = appContext.getBean(SelectionRepository.class);
final List<Selection> selectionList = selectionRepository.findAll();
for (Selection selection : selectionList) {
if (selection.isWatchable()) {
Integer selectionId = selection.getId();

final List<Integer> allUsers = userSavedSelectionRepository.findAllUsers(selectionId);

// Start notification after one notification is made
if (StringUtils.isNotBlank(lastNotificationDate)) {
if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
" Notifying %d users about changes since %s in list %d",
allUsers.size(),
lastNotificationDate,
selectionId
));
}
for (Integer userId : allUsers) {
notify(selectionId, userId);
}
} else {
if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
" Notification of %d users saved selection %d will start on next run. Last notification date was null",
allUsers.size(),
selectionId
));
}
}
}
}

settingManager.setValue(SYSTEM_USER_LASTNOTIFICATIONDATE, nextLastNotificationDate);

if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
"Next notification date for saved selection is now %s.",
nextLastNotificationDate
));
}
}

private void notify(Integer selectionId, Integer userId) {
// Get metadata with changes since last notification
// TODO: Could be relevant to get versionning system info once available
// and report deleted records too.
final List<String> updatedRecords =
userSavedSelectionRepository.findMetadataUpdatedAfter(
selectionId, userId, lastNotificationDate, nextLastNotificationDate);

if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
" Notifying user %d about %d changes since %s in list %d",
userId,
updatedRecords.size(),
lastNotificationDate,
selectionId
));
}

if (updatedRecords.size() > 0) {
// Check if user exists and has an email
// TODO: We should send email depending on user language
User user = userRepository.findOne(userId);
if (StringUtils.isNotEmpty(user.getEmail())) {

// Build message
StringBuffer listOfUpdateMessage = new StringBuffer();
for (String record : updatedRecords) {
try {
listOfUpdateMessage.append(
compileMessageWithIndexFields(recordMessage, record, this.language)
);
} catch (Exception e) {
e.printStackTrace();
}
}

String url = updatedRecordPermalink +
permalinkApp.replace("{{filter}}", String.join(" or ", updatedRecords));
String mailSubject = String.format(subject,
settingManager.getSiteName(), updatedRecords.size(), lastNotificationDate);
String htmlMessage = String.format(message,
listOfUpdateMessage.toString(),
lastNotificationDate,
url, url);

if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
" Sending message with subject %s to user %d",
mailSubject, userId
));
}

// Send email
MailUtil.sendHtmlMail(
Arrays.asList(new String[]{user.getEmail()}),
mailSubject, htmlMessage, settingManager);
}
} else {
if (Log.isDebugEnabled(Geonet.USER_WATCHLIST)) {
Log.debug(Geonet.USER_WATCHLIST, String.format(
" No changes for user %d since %s in his/her list %d",
userId,
lastNotificationDate,
selectionId
));
}
}
}
}
Expand Up @@ -258,16 +258,37 @@ private String buildMetadataChangedMessage(Set<Integer> metadata) {
String curMdDetails = statusMetadataDetails;
// First substitution for variables not stored in the index
curMdDetails = curMdDetails.replace("{{serverurl}}", siteUrl);

for (String f : fields) {
String mdf = XslUtil.getIndexField(null, md.getUuid(), f, this.language);
curMdDetails = curMdDetails.replace("{{index:" + f + "}}", mdf);
}
curMdDetails = compileMessageWithIndexFields(curMdDetails, md.getUuid(), this.language);
message = message.concat(curMdDetails + "\r\n");
}
return message;
}

/**
* Substitute lucene index field values in message.
* Lucene field are identified using {{index:fieldName}} tag.
*
* @param message The message to work on
* @param uuid The record UUID
* @param language The language (define the index to look into)
* @return The message with field substituted by values
*/
public static String compileMessageWithIndexFields(String message, String uuid, String language) {
// Search lucene field to replace
Matcher m = metadataLuceneField.matcher(message);
ArrayList<String> fields = new ArrayList<String>();
while (m.find()) {
fields.add(m.group(1));
}

// First substitution for variables not stored in the index
for (String f : fields) {
String mdf = XslUtil.getIndexField(null, uuid, f, language);
message = message.replace("{{index:" + f + "}}", mdf);
}
return message;
}

private String getTranslatedStatusName(String statusValueId) {
String translatedStatusName = "";
StatusValue s = _statusValueRepository.findOneById(Integer.valueOf(statusValueId));
Expand Down
Expand Up @@ -82,6 +82,7 @@ public class Settings {
public static final String SYSTEM_CSW_METADATA_PUBLIC = "system/csw/metadataPublic";
public static final String SYSTEM_USERSELFREGISTRATION_ENABLE = "system/userSelfRegistration/enable";
public static final String SYSTEM_USERFEEDBACK_ENABLE = "system/userFeedback/enable";
public static final String SYSTEM_USER_LASTNOTIFICATIONDATE = "system/userFeedback/lastNotificationDate";
public static final String SYSTEM_LOCALRATING_ENABLE = "system/localrating/enable";
public static final String SYSTEM_XLINK_RESOLVER_IGNORE = "system/xlinkResolver/ignore";
public static final String SYSTEM_HIDEWITHHELDELEMENTS_ENABLE_LOGGING = "system/hidewithheldelements/enableLogging";
Expand Down

0 comments on commit 10a280d

Please sign in to comment.