/**
* =============================================================================
*
* ORCID (R) Open Source
* http://orcid.org
*
* Copyright (c) 2012-2014 ORCID, Inc.
* Licensed under an MIT-Style License (MIT)
* http://orcid.org/open-source-license
*
* This copyright and license information (including a link to the full license)
* shall be included in its entirety in all copies or substantial portion of
* the software.
*
* =============================================================================
*/
package org.orcid.core.manager.impl;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.orcid.core.exception.OrcidDuplicatedActivityException;
import org.orcid.core.locale.LocaleManager;
import org.orcid.core.manager.NotificationManager;
import org.orcid.core.manager.OrcidSecurityManager;
import org.orcid.core.manager.ProfileEntityCacheManager;
import org.orcid.core.manager.SourceManager;
import org.orcid.core.manager.WorkManager;
import org.orcid.core.manager.read_only.impl.WorkManagerReadOnlyImpl;
import org.orcid.core.manager.validator.ActivityValidator;
import org.orcid.core.manager.validator.ExternalIDValidator;
import org.orcid.core.utils.DisplayIndexCalculatorHelper;
import org.orcid.jaxb.model.common_v2.Visibility;
import org.orcid.jaxb.model.error_v2.OrcidError;
import org.orcid.jaxb.model.notification.amended_v2.AmendedSection;
import org.orcid.jaxb.model.notification.permission_v2.Item;
import org.orcid.jaxb.model.notification.permission_v2.ItemType;
import org.orcid.jaxb.model.record_v2.BulkElement;
import org.orcid.jaxb.model.record_v2.ExternalID;
import org.orcid.jaxb.model.record_v2.Relationship;
import org.orcid.jaxb.model.record_v2.Work;
import org.orcid.jaxb.model.record_v2.WorkBulk;
import org.orcid.persistence.jpa.entities.ProfileEntity;
import org.orcid.persistence.jpa.entities.SourceEntity;
import org.orcid.persistence.jpa.entities.WorkEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.transaction.annotation.Transactional;
public class WorkManagerImpl extends WorkManagerReadOnlyImpl implements WorkManager {
private static final Logger LOGGER = LoggerFactory.getLogger(WorkManagerImpl.class);
@Resource
private SourceManager sourceManager;
@Resource
private OrcidSecurityManager orcidSecurityManager;
@Resource
private ProfileEntityCacheManager profileEntityCacheManager;
@Resource
private NotificationManager notificationManager;
@Resource
private ExternalIDValidator externalIDValidator;
@Resource
private ActivityValidator activityValidator;
@Resource
private MessageSource messageSource;
@Resource
private LocaleManager localeManager;
@Value("${org.orcid.core.works.bulk.max:100}")
private Long maxBulkSize;
/**
* Updates the visibility of an existing work
*
* @param workId
* The id of the work that will be updated
* @param visibility
* The new visibility value for the profile work relationship
* @return true if the relationship was updated
* */
public boolean updateVisibilities(String orcid, List<Long> workIds, Visibility visibility) {
return workDao.updateVisibilities(orcid, workIds, visibility);
}
/**
* Removes a work.
*
* @param workId
* The id of the work that will be removed from the client
* profile
* @param clientOrcid
* The client orcid
* @return true if the work was deleted
* */
public boolean removeWorks(String clientOrcid, List<Long> workIds) {
return workDao.removeWorks(clientOrcid, workIds);
}
@Override
public void removeAllWorks(String orcid) {
workDao.removeWorks(orcid);
}
/**
* Sets the display index of the new work
*
* @param orcid
* The work owner
* @param workId
* The work id
* @return true if the work index was correctly set
* */
public boolean updateToMaxDisplay(String orcid, Long workId) {
return workDao.updateToMaxDisplay(orcid, workId);
}
@Override
@Transactional
public Work createWork(String orcid, Work work, boolean isApiRequest) {
SourceEntity sourceEntity = sourceManager.retrieveSourceEntity();
if (isApiRequest) {
activityValidator.validateWork(work, sourceEntity, true, isApiRequest, null);
// If it is the user adding the peer review, allow him to add
// duplicates
if (!sourceEntity.getSourceId().equals(orcid)) {
long lastModifiedTime = getLastModified(orcid);
List<Work> existingWorks = this.findWorks(orcid, lastModifiedTime);
if (existingWorks != null) {
for (Work existing : existingWorks) {
activityValidator.checkExternalIdentifiersForDuplicates(work.getExternalIdentifiers(), existing.getExternalIdentifiers(), existing.getSource(),
sourceEntity);
}
}
}
} else {
// validate external ID vocab
externalIDValidator.validateWorkOrPeerReview(work.getExternalIdentifiers());
}
WorkEntity workEntity = jpaJaxbWorkAdapter.toWorkEntity(work);
ProfileEntity profile = profileEntityCacheManager.retrieve(orcid);
workEntity.setProfile(profile);
workEntity.setAddedToProfileDate(new Date());
// Set source id
if(sourceEntity.getSourceProfile() != null) {
workEntity.setSourceId(sourceEntity.getSourceProfile().getId());
}
if(sourceEntity.getSourceClient() != null) {
workEntity.setClientSourceId(sourceEntity.getSourceClient().getId());
}
setIncomingWorkPrivacy(workEntity, profile);
DisplayIndexCalculatorHelper.setDisplayIndexOnNewEntity(workEntity, isApiRequest);
workDao.persist(workEntity);
workDao.flush();
notificationManager.sendAmendEmail(orcid, AmendedSection.WORK, createItem(workEntity));
return jpaJaxbWorkAdapter.toWork(workEntity);
}
/**
* Add a list of works to the given profile
*
* @param works
* The list of works that want to be added
* @param orcid
* The id of the user we want to add the works to
*
* @return the work bulk with the put codes of the new works or the error
* that indicates why a work can't be added
*/
@Override
@Transactional
public WorkBulk createWorks(String orcid, WorkBulk workBulk) {
SourceEntity sourceEntity = sourceManager.retrieveSourceEntity();
Set<ExternalID> existingExternalIdentifiers = buildExistingExternalIdsSet(orcid, sourceEntity.getSourceId());
if(workBulk.getBulk() != null && !workBulk.getBulk().isEmpty()) {
List<BulkElement> bulk = workBulk.getBulk();
//Check bulk size
if(bulk.size() > maxBulkSize) {
Locale locale = localeManager.getLocale();
throw new IllegalArgumentException(messageSource.getMessage("apiError.validation_too_many_elements_in_bulk.exception", new Object[]{maxBulkSize}, locale));
}
for(int i = 0; i < bulk.size(); i++) {
if(Work.class.isAssignableFrom(bulk.get(i).getClass())){
Work work = (Work) bulk.get(i);
try {
//Validate the work
activityValidator.validateWork(work, sourceEntity, true, true, null);
//Validate it is not duplicated
if(work.getExternalIdentifiers() != null) {
for(ExternalID extId : work.getExternalIdentifiers().getExternalIdentifier()) {
if(existingExternalIdentifiers.contains(extId)) {
Map<String, String> params = new HashMap<String, String>();
params.put("clientName", sourceEntity.getSourceName());
throw new OrcidDuplicatedActivityException(params);
}
}
}
//Save the work
WorkEntity workEntity = jpaJaxbWorkAdapter.toWorkEntity(work);
ProfileEntity profile = profileEntityCacheManager.retrieve(orcid);
workEntity.setProfile(profile);
workEntity.setAddedToProfileDate(new Date());
// Set source id
if(sourceEntity.getSourceProfile() != null) {
workEntity.setSourceId(sourceEntity.getSourceProfile().getId());
}
if(sourceEntity.getSourceClient() != null) {
workEntity.setClientSourceId(sourceEntity.getSourceClient().getId());
}
setIncomingWorkPrivacy(workEntity, profile);
DisplayIndexCalculatorHelper.setDisplayIndexOnNewEntity(workEntity, true);
workDao.persist(workEntity);
//Update the element in the bulk
Work updatedWork = jpaJaxbWorkAdapter.toWork(workEntity);
bulk.set(i, updatedWork);
//Add the work extIds to the list of existing external identifiers
addExternalIdsToExistingSet(updatedWork, existingExternalIdentifiers);
} catch(Exception e) {
//Get the exception
OrcidError orcidError = orcidCoreExceptionMapper.getOrcidError(e);
bulk.set(i, orcidError);
}
}
}
workDao.flush();
}
return workBulk;
}
/**
* Return the list of existing external identifiers for the given user where the source matches the given sourceId
*
* @param orcid
* The user we want to add the works to
* @param sourceId
* The client id we are evaluating
* @return A set of all the existing external identifiers that belongs to the given user and to the given source id
* */
private Set<ExternalID> buildExistingExternalIdsSet(String orcid, String sourceId) {
Set<ExternalID> existingExternalIds = new HashSet<ExternalID>();
long lastModifiedTime = getLastModified(orcid);
List<Work> existingWorks = this.findWorks(orcid, lastModifiedTime);
for(Work work : existingWorks) {
//If it is the same source
if(work.retrieveSourcePath().equals(sourceId)) {
if(work.getExternalIdentifiers() != null && work.getExternalIdentifiers().getExternalIdentifier() != null) {
for(ExternalID extId : work.getExternalIdentifiers().getExternalIdentifier()) {
//Don't include PART_OF external ids
if(!Relationship.PART_OF.equals(extId.getRelationship())) {
existingExternalIds.add(extId);
}
}
}
}
}
return existingExternalIds;
}
private void addExternalIdsToExistingSet(Work work, Set<ExternalID> existingExternalIDs) {
if(work != null && work.getExternalIdentifiers() != null && work.getExternalIdentifiers().getExternalIdentifier() != null) {
for(ExternalID extId : work.getExternalIdentifiers().getExternalIdentifier()) {
//Don't include PART_OF external ids
if(!Relationship.PART_OF.equals(extId.getRelationship())) {
existingExternalIDs.add(extId);
}
}
}
}
@Override
@Transactional
public Work updateWork(String orcid, Work work, boolean isApiRequest) {
WorkEntity workEntity = workDao.getWork(orcid, work.getPutCode());
Visibility originalVisibility = workEntity.getVisibility();
SourceEntity sourceEntity = sourceManager.retrieveSourceEntity();
//Save the original source
String existingSourceId = workEntity.getSourceId();
String existingClientSourceId = workEntity.getClientSourceId();
if (isApiRequest) {
activityValidator.validateWork(work, sourceEntity, false, isApiRequest, workEntity.getVisibility());
long lastModifiedTime = getLastModified(orcid);
List<Work> existingWorks = this.findWorks(orcid, lastModifiedTime);
for (Work existing : existingWorks) {
// Dont compare the updated peer review with the DB version
if (!existing.getPutCode().equals(work.getPutCode())) {
activityValidator.checkExternalIdentifiersForDuplicates(work.getExternalIdentifiers(), existing.getExternalIdentifiers(), existing.getSource(), sourceEntity);
}
}
}else{
//validate external ID vocab
externalIDValidator.validateWorkOrPeerReview(work.getExternalIdentifiers());
}
orcidSecurityManager.checkSource(workEntity);
jpaJaxbWorkAdapter.toWorkEntity(work, workEntity);
workEntity.setVisibility(originalVisibility);
//Be sure it doesn't overwrite the source
workEntity.setSourceId(existingSourceId);
workEntity.setClientSourceId(existingClientSourceId);
workDao.merge(workEntity);
workDao.flush();
notificationManager.sendAmendEmail(orcid, AmendedSection.WORK, createItem(workEntity));
return jpaJaxbWorkAdapter.toWork(workEntity);
}
@Override
public boolean checkSourceAndRemoveWork(String orcid, Long workId) {
boolean result = true;
WorkEntity workEntity = workDao.getWork(orcid, workId);
orcidSecurityManager.checkSource(workEntity);
try {
Item item = createItem(workEntity);
workDao.removeWork(orcid, workId);
workDao.flush();
notificationManager.sendAmendEmail(orcid, AmendedSection.WORK, item);
} catch (Exception e) {
LOGGER.error("Unable to delete work with ID: " + workId);
result = false;
}
return result;
}
private void setIncomingWorkPrivacy(WorkEntity workEntity, ProfileEntity profile) {
Visibility incomingWorkVisibility = workEntity.getVisibility();
Visibility defaultWorkVisibility = profile.getActivitiesVisibilityDefault();
if (profile.getClaimed()) {
workEntity.setVisibility(defaultWorkVisibility);
} else if (incomingWorkVisibility == null) {
workEntity.setVisibility(org.orcid.jaxb.model.common_v2.Visibility.PRIVATE);
}
}
private Item createItem(WorkEntity workEntity) {
Item item = new Item();
item.setItemName(workEntity.getTitle());
item.setItemType(ItemType.WORK);
item.setPutCode(String.valueOf(workEntity.getId()));
return item;
}
}