[CST-7756] porting of the script for notification of content subscription

This commit is contained in:
Mykhaylo
2022-12-14 16:14:48 +01:00
parent 25b8ba8ead
commit 57f917ae0e
17 changed files with 1069 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.crosswalk;
import static org.dspace.content.Item.ANY;
import java.io.OutputStream;
import java.io.PrintStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.factory.HandleServiceFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Creates a String to be sent as email body for subscriptions
*
* @author Alba Aliu
*/
public class SubscriptionDsoMetadataForEmailCompose implements StreamDisseminationCrosswalk {
private List<String> metadata = new ArrayList<>();
@Autowired
private ItemService itemService;
@Override
public boolean canDisseminate(Context context, DSpaceObject dso) {
return Objects.nonNull(dso) && dso.getType() == Constants.ITEM;
}
@Override
public void disseminate(Context context, DSpaceObject dso, OutputStream out) throws SQLException {
if (dso.getType() == Constants.ITEM) {
Item item = (Item) dso;
PrintStream printStream = new PrintStream(out);
for (String actualMetadata : metadata) {
String[] splitted = actualMetadata.split("\\.");
String qualifier = null;
if (splitted.length == 1) {
qualifier = splitted[2];
}
var metadataValue = itemService.getMetadataFirstValue(item, splitted[0], splitted[1], qualifier, ANY);
printStream.print(metadataValue + " ");
}
String itemURL = HandleServiceFactory.getInstance()
.getHandleService()
.resolveToURL(context, item.getHandle());
printStream.print(itemURL);
printStream.print("\n");
printStream.close();
}
}
@Override
public String getMIMEType() {
return "text/plain";
}
public List<String> getMetadata() {
return metadata;
}
public void setMetadata(List<String> metadata) {
this.metadata = metadata;
}
}

View File

@@ -139,4 +139,23 @@ public class DiscoveryConfigurationService {
}
}
}
/**
* Retrieves a list of all DiscoveryConfiguration objects where key starts with prefixConfigurationName
*
* @param prefixConfigurationName string as prefix key
*/
public List<DiscoveryConfiguration> getDiscoveryConfigurationWithPrefixName(final String prefixConfigurationName) {
List<DiscoveryConfiguration> discoveryConfigurationList = new ArrayList<>();
if (StringUtils.isNotBlank(prefixConfigurationName)) {
for (String key : map.keySet()) {
if (key.equals(prefixConfigurationName) || key.startsWith(prefixConfigurationName)) {
DiscoveryConfiguration config = map.get(key);
discoveryConfigurationList.add(config);
}
}
}
return discoveryConfigurationList;
}
}

View File

@@ -0,0 +1,16 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.discovery.configuration;
/**
* This class extends {@link DiscoveryConfiguration} and add method for set parameters
* to filter query list
*
* @author Danilo Di Nuzzo (danilo.dinuzzo at 4science.it)
*/
public class DiscoveryRelatedItemConfiguration extends DiscoveryConfiguration {}

View File

@@ -0,0 +1,66 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.discovery.configuration;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
*
* Extension of {@link DiscoverySortFieldConfiguration} used to configure sorting
* taking advantage of solr function feature.
*
* Order is evaluated by mean of function parameter value and passed in arguments as input.
*
* @author Corrado Lombardi (corrado.lombardi at 4science.it)
*
*/
public class DiscoverySortFunctionConfiguration extends DiscoverySortFieldConfiguration {
public static final String SORT_FUNCTION = "sort_function";
private String function;
private List<String> arguments;
private String id;
public void setFunction(final String function) {
this.function = function;
}
public void setArguments(final List<String> arguments) {
this.arguments = arguments;
}
@Override
public String getType() {
return SORT_FUNCTION;
}
@Override
public String getMetadataField() {
return id;
}
public void setId(final String id) {
this.id = id;
}
/**
* Returns the function to be used by solr to sort result
* @param functionArgs variable arguments to be inserted in function
* @return
*/
public String getFunction(final Serializable... functionArgs) {
final String args = String.join(",", Optional.ofNullable(arguments).orElse(Collections.emptyList()));
final String result = function + "(" + args + ")";
return MessageFormat.format(result, functionArgs);
}
}

View File

@@ -0,0 +1,94 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang.StringUtils.EMPTY;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.StreamDisseminationCrosswalk;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.discovery.IndexableObject;
import org.dspace.eperson.EPerson;
import org.dspace.subscriptions.service.SubscriptionGenerator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation class of SubscriptionGenerator
* which will handle the logic of sending the emails
* in case of content subscriptions
*/
@SuppressWarnings("rawtypes")
public class ContentGenerator implements SubscriptionGenerator<IndexableObject> {
private final Logger log = LogManager.getLogger(ContentGenerator.class);
@SuppressWarnings("unchecked")
@Resource(name = "entityDissemination")
private Map<String, StreamDisseminationCrosswalk> mapEntityDisseminatorProperty = new HashMap();
@Autowired
private ItemService itemService;
@Override
public void notifyForSubscriptions(Context context, EPerson ePerson,
List<IndexableObject> indexableComm,
List<IndexableObject> indexableColl,
List<IndexableObject> indexableItems) {
try {
if (Objects.nonNull(ePerson)) {
Locale supportedLocale = I18nUtil.getEPersonLocale(ePerson);
Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "subscriptions_content"));
email.addRecipient(ePerson.getEmail());
email.addArgument(generateHtmlBodyMail(context, indexableComm));
email.addArgument(generateHtmlBodyMail(context, indexableColl));
email.addArgument(generateHtmlBodyMail(context, indexableItems));
email.send();
}
} catch (Exception e) {
// log this email error
log.warn("Cannot email user eperson_id:" + ePerson.getID() + " eperson_email:" + ePerson.getEmail());
}
}
private String generateHtmlBodyMail(Context context, List<IndexableObject> indexableObjects) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("\n".getBytes(UTF_8));
if (indexableObjects.size() > 0) {
for (IndexableObject indexableObject : indexableObjects) {
out.write("\n".getBytes(UTF_8));
Item item = (Item) indexableObject.getIndexedObject();
var entityType = itemService.getEntityTypeLabel(item);
mapEntityDisseminatorProperty.get(entityType).disseminate(context, item, out);
}
return out.toString();
} else {
out.write("No items".getBytes(UTF_8));
}
return out.toString();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return EMPTY;
}
}

View File

@@ -0,0 +1,91 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
import java.sql.SQLException;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.utils.DSpace;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*
* @author alba aliu
*/
public class SubscriptionEmailNotification
extends DSpaceRunnable<SubscriptionEmailNotificationConfiguration<SubscriptionEmailNotification>> {
private Context context;
private SubscriptionEmailNotificationService subscriptionEmailNotificationService;
@Override
@SuppressWarnings("unchecked")
public SubscriptionEmailNotificationConfiguration<SubscriptionEmailNotification> getScriptConfiguration() {
return new DSpace().getServiceManager().getServiceByName("subscription-send",
SubscriptionEmailNotificationConfiguration.class);
}
@Override
public void setup() throws ParseException {
this.subscriptionEmailNotificationService = new DSpace().getServiceManager().getServiceByName(
SubscriptionEmailNotificationService.class.getName(), SubscriptionEmailNotificationService.class);
}
@Override
public void internalRun() throws Exception {
assignCurrentUserInContext();
assignSpecialGroupsInContext();
String frequencyOption = commandLine.getOptionValue("f");
if (StringUtils.isBlank(frequencyOption)) {
throw new IllegalArgumentException("Option frequency f must be set");
}
if (!frequencyOption.equals("D") && !frequencyOption.equals("M") && !frequencyOption.equals("W")) {
throw new IllegalArgumentException("Option f must be D, M or W");
}
subscriptionEmailNotificationService.perform(getContext(), handler, "content", frequencyOption);
}
protected void assignCurrentUserInContext() throws SQLException {
context = new Context();
UUID uuid = getEpersonIdentifier();
if (Objects.nonNull(uuid)) {
EPerson ePerson = EPersonServiceFactory.getInstance().getEPersonService().find(context, uuid);
context.setCurrentUser(ePerson);
}
}
private void assignSpecialGroupsInContext() throws SQLException {
for (UUID uuid : handler.getSpecialGroups()) {
context.setSpecialGroup(uuid);
}
}
public SubscriptionEmailNotificationService getSubscriptionEmailNotificationService() {
return subscriptionEmailNotificationService;
}
public void setSubscriptionEmailNotificationService(SubscriptionEmailNotificationService subscriptionNotifService) {
this.subscriptionEmailNotificationService = subscriptionNotifService;
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
}

View File

@@ -0,0 +1,15 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
/**
* Extension of {@link SubscriptionEmailNotification} for CLI.
*/
public class SubscriptionEmailNotificationCli extends SubscriptionEmailNotification {
}

View File

@@ -0,0 +1,16 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
/**
* Extension of {@link SubscriptionEmailNotificationCli} for CLI.
*/
public class SubscriptionEmailNotificationCliScriptConfiguration<T extends SubscriptionEmailNotificationCli>
extends SubscriptionEmailNotificationConfiguration<T> {
}

View File

@@ -0,0 +1,62 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
import java.sql.SQLException;
import java.util.Objects;
import org.apache.commons.cli.Options;
import org.dspace.authorize.AuthorizeServiceImpl;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*/
public class SubscriptionEmailNotificationConfiguration<T
extends SubscriptionEmailNotification> extends ScriptConfiguration<T> {
private Class<T> dspaceRunnableClass;
@Autowired
private AuthorizeServiceImpl authorizeService;
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
@Override
public Options getOptions() {
if (Objects.isNull(options)) {
Options options = new Options();
options.addOption("f", "Frequency", true, "Subscription frequency. It can have value D, M or W");
options.getOption("f").setRequired(true);
super.options = options;
}
return options;
}
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
}

View File

@@ -0,0 +1,144 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.IndexableObject;
import org.dspace.eperson.Subscription;
import org.dspace.eperson.service.SubscribeService;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
import org.dspace.subscriptions.service.SubscriptionGenerator;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
/**
* Implementation of {@link DSpaceRunnable} to find subscribed objects and send notification mails about them
*
* @author alba aliu
*/
public class SubscriptionEmailNotificationService {
private static final Logger log = LogManager.getLogger(SubscriptionEmailNotification.class);
public static final List<String> FREQUENCIES = Arrays.asList("D", "W", "M");
private Map<String, DSpaceObjectUpdates> contentUpdates = new HashMap<>();
@SuppressWarnings("rawtypes")
private Map<String, SubscriptionGenerator> generators = new HashMap<>();
@SuppressWarnings("rawtypes")
private List<IndexableObject> communities = new ArrayList<>();
@SuppressWarnings("rawtypes")
private List<IndexableObject> collections = new ArrayList<>();
@SuppressWarnings("rawtypes")
private List<IndexableObject> items = new ArrayList<>();
private final SubscribeService subscribeService;
@SuppressWarnings("unchecked")
public void perform(Context context, DSpaceRunnableHandler handler, String type, String frequency) {
try {
context.turnOffAuthorisationSystem();
List<Subscription> subscriptionList = findAllSubscriptionsByTypeAndFrequency(context, type, frequency);
// if content subscription
// Here is verified if type is "content" Or "statistics" as them are configured
if (type.equals(generators.keySet().toArray()[0])) {
// the list of the person who has subscribed
int iterator = 0;
for (Subscription subscription : subscriptionList) {
DSpaceObject dSpaceObject = getdSpaceObject(subscription);
if (dSpaceObject instanceof Community) {
communities.addAll(contentUpdates.get(Community.class.getSimpleName().toLowerCase(Locale.ROOT))
.findUpdates(context, dSpaceObject, frequency));
} else if (dSpaceObject instanceof Collection) {
collections.addAll(contentUpdates.get(Collection.class.getSimpleName().toLowerCase(Locale.ROOT))
.findUpdates(context, dSpaceObject, frequency));
} else if (dSpaceObject instanceof Item) {
items.addAll(contentUpdates.get(Item.class.getSimpleName().toLowerCase(Locale.ROOT))
.findUpdates(context, dSpaceObject, frequency));
}
var ePerson = subscription.getePerson();
if (iterator < subscriptionList.size() - 1) {
if (ePerson.equals(subscriptionList.get(iterator + 1).getePerson())) {
iterator++;
continue;
} else {
generators.get(type)
.notifyForSubscriptions(context, ePerson, communities, collections, items);
communities.clear();
collections.clear();
items.clear();
}
} else {
//in the end of the iteration
generators.get(type).notifyForSubscriptions(context, ePerson, communities, collections, items);
}
iterator++;
}
} else {
throw new IllegalArgumentException("Currently this type:" + type +
" of subscription is not supported!");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
handler.handleException(e);
context.abort();
} finally {
context.restoreAuthSystemState();
}
}
private DSpaceObject getdSpaceObject(Subscription subscription) {
DSpaceObject dSpaceObject = subscription.getdSpaceObject();
if (subscription.getdSpaceObject() instanceof HibernateProxy) {
HibernateProxy hibernateProxy = (HibernateProxy) subscription.getdSpaceObject();
LazyInitializer initializer = hibernateProxy.getHibernateLazyInitializer();
dSpaceObject = (DSpaceObject) initializer.getImplementation();
}
return dSpaceObject;
}
private List<Subscription> findAllSubscriptionsByTypeAndFrequency(Context context, String type, String frequency) {
try {
return this.subscribeService.findAllSubscriptionsByTypeAndFrequency(context, type, frequency)
.stream()
.sorted(Comparator.comparing(s -> s.getePerson().getID()))
.collect(Collectors.toList());
} catch (SQLException e) {
log.error(e.getMessage());
}
return new ArrayList<Subscription>();
}
@SuppressWarnings("rawtypes")
public SubscriptionEmailNotificationService(SubscribeService subscribeService,
Map<String, SubscriptionGenerator> generators,
Map<String, DSpaceObjectUpdates> contentUpdates) {
this.subscribeService = subscribeService;
this.generators = generators;
this.contentUpdates = contentUpdates;
}
}

View File

@@ -0,0 +1,48 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions.dSpaceObjectsUpdates;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
/**
* Class which will be used to find
* all collection objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public class CollectionsUpdates implements DSpaceObjectUpdates {
private final SearchService searchService;
@Override
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName());
discoverQuery.addFilterQueries("location.coll:(" + dSpaceObject.getID() + ")");
discoverQuery.addFilterQueries("lastModified_dt:" + findLastFrequency(frequency));
DiscoverResult discoverResult = searchService.search(context, discoverQuery);
return discoverResult.getIndexableObjects();
}
public CollectionsUpdates(SearchService searchService) {
this.searchService = searchService;
}
}

View File

@@ -0,0 +1,48 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions.dSpaceObjectsUpdates;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
/**
* Class which will be used to find
* all community objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public class CommunityUpdates implements DSpaceObjectUpdates {
private SearchService searchService;
@Override
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.addFilterQueries("search.resourcetype:" + Item.class.getSimpleName());
discoverQuery.addFilterQueries("location.comm:(" + dSpaceObject.getID() + ")");
discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency));
DiscoverResult discoverResult = searchService.search(context, discoverQuery);
return discoverResult.getIndexableObjects();
}
public CommunityUpdates(SearchService searchService) {
this.searchService = searchService;
}
}

View File

@@ -0,0 +1,193 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions.dSpaceObjectsUpdates;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.configuration.DiscoveryConfiguration;
import org.dspace.discovery.configuration.DiscoveryConfigurationService;
import org.dspace.discovery.configuration.DiscoveryRelatedItemConfiguration;
import org.dspace.discovery.configuration.DiscoverySortConfiguration;
import org.dspace.discovery.configuration.DiscoverySortFieldConfiguration;
import org.dspace.discovery.configuration.DiscoverySortFunctionConfiguration;
import org.dspace.discovery.indexobject.IndexableCollection;
import org.dspace.discovery.indexobject.IndexableCommunity;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.subscriptions.service.DSpaceObjectUpdates;
/**
* Class which will be used to find
* all item objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public class ItemsUpdates implements DSpaceObjectUpdates {
private final Logger log = LogManager.getLogger(ItemsUpdates.class);
private final CollectionService collectionService;
private final CommunityService communityService;
private final ItemService itemService;
private DiscoveryConfigurationService searchConfigurationService;
private SearchService searchService;
@Override
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency) {
List<IndexableObject> list = new ArrayList<>();
// entity type found
String inverseRelationName = "RELATION." + itemService.getEntityTypeLabel((Item) dSpaceObject);
List<DiscoveryConfiguration> discoveryConfigurationList =
searchConfigurationService.getDiscoveryConfigurationWithPrefixName(inverseRelationName);
DiscoverQuery discoverQuery = null;
DiscoverResult searchResult = null;
IndexableObject indexableObject = resolveScope(context, dSpaceObject.getID().toString());
try {
for (DiscoveryConfiguration discoveryConfiguration : discoveryConfigurationList) {
discoverQuery = buildDiscoveryQuery(discoveryConfiguration, indexableObject);
discoverQuery.addFilterQueries("lastModified_dt:" + this.findLastFrequency(frequency));
searchResult = searchService.search(context, discoverQuery);
list.addAll(searchResult.getIndexableObjects());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return list;
}
private IndexableObject<?, ?> resolveScope(Context context, String scope) {
IndexableObject<?, ?> scopeObj = null;
if (StringUtils.isBlank(scope)) {
return scopeObj;
}
try {
UUID uuid = UUID.fromString(scope);
scopeObj = new IndexableCommunity(communityService.find(context, uuid));
if (scopeObj.getIndexedObject() == null) {
scopeObj = new IndexableCollection(collectionService.find(context, uuid));
}
if (scopeObj.getIndexedObject() == null) {
scopeObj = new IndexableItem(itemService.find(context, uuid));
}
} catch (IllegalArgumentException e) {
log.error("The given scope string " + trimToEmpty(scope) + " is not a UUID");
} catch (SQLException e) {
log.error("Unable to retrieve DSpace Object with ID " + trimToEmpty(scope) + " from the database");
}
return scopeObj;
}
private DiscoverQuery buildDiscoveryQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject<?,?> scope)
throws SQLException {
DiscoverQuery discoverQuery = buildBaseQuery(discoveryConfiguration, scope);
discoverQuery.addDSpaceObjectFilter(IndexableItem.TYPE);
configureSorting(discoverQuery, discoveryConfiguration.getSearchSortConfiguration(), scope);
return discoverQuery;
}
@SuppressWarnings("rawtypes")
private void configureSorting(DiscoverQuery queryArgs, DiscoverySortConfiguration searchSortConfiguration,
final IndexableObject scope) {
String sortBy = getDefaultSortField(searchSortConfiguration);
String sortOrder = getDefaultSortDirection(searchSortConfiguration);
//Update Discovery query
DiscoverySortFieldConfiguration sortFieldConfiguration = searchSortConfiguration
.getSortFieldConfiguration(sortBy);
if (Objects.nonNull(sortFieldConfiguration)) {
String sortField;
if (DiscoverySortFunctionConfiguration.SORT_FUNCTION.equals(sortFieldConfiguration.getType())) {
sortField = MessageFormat.format(
((DiscoverySortFunctionConfiguration) sortFieldConfiguration).getFunction(scope.getID()),
scope.getID());
} else {
var type = sortFieldConfiguration.getType();
var metadataField = sortFieldConfiguration.getMetadataField();
sortField = searchService.toSortFieldIndex(metadataField, type);
}
if ("asc".equalsIgnoreCase(sortOrder)) {
queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.asc);
} else if ("desc".equalsIgnoreCase(sortOrder)) {
queryArgs.setSortField(sortField, DiscoverQuery.SORT_ORDER.desc);
} else {
log.error(sortOrder + " is not a valid sort order");
}
} else {
log.error(sortBy + " is not a valid sort field");
}
}
private String getDefaultSortDirection(DiscoverySortConfiguration searchSortConfiguration) {
return searchSortConfiguration.getSortFields().iterator().next().getDefaultSortOrder().toString();
}
private String getDefaultSortField(DiscoverySortConfiguration searchSortConfiguration) {
String sortBy;// Attempt to find the default one, if none found we use SCORE
sortBy = "score";
if (Objects.nonNull(searchSortConfiguration.getSortFields()) &&
!searchSortConfiguration.getSortFields().isEmpty()) {
DiscoverySortFieldConfiguration defaultSort = searchSortConfiguration.getSortFields().get(0);
if (org.apache.commons.lang.StringUtils.isBlank(defaultSort.getMetadataField())) {
return sortBy;
}
sortBy = defaultSort.getMetadataField();
}
return sortBy;
}
private DiscoverQuery buildBaseQuery(DiscoveryConfiguration discoveryConfiguration, IndexableObject<?, ?> scope) {
DiscoverQuery discoverQuery = new DiscoverQuery();
if (Objects.isNull(discoveryConfiguration)) {
return discoverQuery;
}
discoverQuery.setDiscoveryConfigurationName(discoveryConfiguration.getId());
List<String> filterQueries = discoveryConfiguration.getDefaultFilterQueries();
for (String filterQuery : filterQueries) {
if (discoveryConfiguration instanceof DiscoveryRelatedItemConfiguration) {
discoverQuery.addFilterQueries(MessageFormat.format(filterQuery, scope.getID()));
} else {
discoverQuery.addFilterQueries(filterQuery);
}
}
return discoverQuery;
}
public ItemsUpdates(CollectionService collectionService, CommunityService communityService, ItemService itemService,
DiscoveryConfigurationService searchConfigurationService, SearchService searchService) {
this.collectionService = collectionService;
this.communityService = communityService;
this.itemService = itemService;
this.searchConfigurationService = searchConfigurationService;
this.searchService = searchService;
}
}

View File

@@ -0,0 +1,70 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions.service;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import org.dspace.content.DSpaceObject;
import org.dspace.core.Context;
import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchServiceException;
/**
* Interface class which will be used to find all objects updated related with subscribed DSO
*
* @author Alba Aliu
*/
public interface DSpaceObjectUpdates {
/**
* Send an email to some addresses, concerning a Subscription, using a given dso.
*
* @param context current DSpace session.
*/
@SuppressWarnings("rawtypes")
public List<IndexableObject> findUpdates(Context context, DSpaceObject dSpaceObject, String frequency)
throws SearchServiceException;
default String findLastFrequency(String frequency) {
String startDate = "";
String endDate = "";
Calendar cal = Calendar.getInstance();
// Full ISO 8601 is e.g.
SimpleDateFormat fullIsoStart = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00'Z'");
SimpleDateFormat fullIsoEnd = new SimpleDateFormat("yyyy-MM-dd'T'23:59:59'Z'");
switch (frequency) {
case "D":
cal.add(Calendar.DAY_OF_MONTH, -1);
endDate = fullIsoEnd.format(cal.getTime());
startDate = fullIsoStart.format(cal.getTime());
break;
case "M":
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
cal.add(Calendar.DAY_OF_MONTH, -dayOfMonth);
endDate = fullIsoEnd.format(cal.getTime());
cal.add(Calendar.MONTH, -1);
cal.add(Calendar.DAY_OF_MONTH, 1);
startDate = fullIsoStart.format(cal.getTime());
break;
case "W":
cal.add(Calendar.DAY_OF_WEEK, -1);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1;
cal.add(Calendar.DAY_OF_WEEK, -dayOfWeek);
endDate = fullIsoEnd.format(cal.getTime());
cal.add(Calendar.DAY_OF_WEEK, -6);
startDate = fullIsoStart.format(cal.getTime());
break;
default:
return null;
}
return "[" + startDate + " TO " + endDate + "]";
}
}

View File

@@ -0,0 +1,25 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.subscriptions.service;
import java.util.List;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
/**
* Interface Class which will be used to send email notifications to ePerson
* containing information for all list of objects.
*
* @author Alba Aliu
*/
public interface SubscriptionGenerator<T> {
public void notifyForSubscriptions(Context c, EPerson ePerson, List<T> comm, List<T> coll, List<T> items);
}

View File

@@ -80,4 +80,10 @@
<property name="description" value="Batch Export to Simple Archive Format (SAF)"/>
<property name="dspaceRunnableClass" value="org.dspace.app.itemexport.ItemExportCLI"/>
</bean>
<bean id="subscription-send" class="org.dspace.subscriptions.SubscriptionEmailNotificationCliScriptConfiguration">
<property name="description" value="Perform subscriptions send"/>
<property name="dspaceRunnableClass" value="org.dspace.subscriptions.SubscriptionEmailNotificationCli"/>
</bean>
</beans>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The contents of this file are subject to the license and copyright
detailed in the LICENSE and NOTICE files at the root of the source
tree and available online at
http://www.dspace.org/license/
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean class="org.dspace.subscriptions.SubscriptionEmailNotificationService">
<constructor-arg name="generators">
<map>
<entry key="content" value-ref="contentNotifyGenerator"/>
</map>
</constructor-arg>
<constructor-arg name="contentUpdates">
<map>
<entry key="community" value-ref="communityUpdates"/>
<entry key="collection" value-ref="collectionUpdates"/>
<entry key="item" value-ref="itemUpdates"/>
</map>
</constructor-arg>
</bean>
<util:map id="generators">
<entry key="content" value-ref="contentNotifyGenerator"/>
</util:map>
<bean id="contentNotifyGenerator" name="contentNotifyGenerator" class="org.dspace.subscriptions.ContentGenerator" />
<util:map id="contentUpdates">
<entry key="community" value-ref="communityUpdates"/>
<entry key="collection" value-ref="collectionUpdates"/>
<entry key="item" value-ref="itemUpdates"/>
</util:map>
<util:list id="generalMetadataEntity">
<value>dc.title</value>
</util:list>
<util:map id="entityDissemination">
<entry key="Publication" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Person" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Journal" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="OrgUnit" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Dataset" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Patent" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Event" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Equipment" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="JournalVolume" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Project" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="PersonCv" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="JournalIssue" value-ref="subscriptionDsoMetadataForEmailCompose"/>
<entry key="Funding" value-ref="subscriptionDsoMetadataForEmailCompose"/>
</util:map>
<bean class="org.dspace.content.crosswalk.SubscriptionDsoMetadataForEmailCompose" id="subscriptionDsoMetadataForEmailCompose">
<property name="metadata" ref ="generalMetadataEntity"/>
</bean>
<bean id="communityUpdates" name="communityUpdates" class="org.dspace.subscriptions.dSpaceObjectsUpdates.CommunityUpdates" />
<bean id="collectionUpdates" name="collectionUpdates" class="org.dspace.subscriptions.dSpaceObjectsUpdates.CollectionsUpdates" />
<bean id="itemUpdates" name="itemUpdates" class="org.dspace.subscriptions.dSpaceObjectsUpdates.ItemsUpdates" />
</beans>