Files
alfresco-community-repo/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
Alan Davis 9f341cc361 Reverse Merge WITHOUT mergeinfo a commit that was merged to HEAD via API-STRIKES-BACK and 5.2.N!
127890 Merged 5.2.N (5.2.1) to HEAD (5.2)
      127465 aleahu: Merged 5.1.N (5.1.2) to 5.2.N (5.2.1)
         127454 jkaabimofrad: MNT-16224, RA-1093: Fixed Quickshare issue where deleting a shared node didn't remove the 'shared' aspect. The fix also takes care of the shared nodes that have been deleted but not yet restored, as well as, shared nodes that have already been restored from the trashcan (allow the user to un-share).
   - ENDED UP WITH DUPLICATE CODE ALREADY MERGED BY:
      127615 Merged API-STRIKES-BACK (5.2.0) to HEAD (5.2)
         127462 jkaabimofrad: Merged 5.1.N (5.1.2) to API-STRIKES-BACK (5.2.0)
            127454 jkaabimofrad: MNT-16224, RA-1093: Fixed Quickshare issue where deleting a shared node didn't remove the 'shared' aspect. The fix also takes care of the shared nodes that have been deleted but not yet restored, as well as, shared nodes that have already been restored from the trashcan (allow the user to un-share).


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@127898 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2016-06-06 10:56:02 +00:00

1099 lines
39 KiB
Java

/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.quickshare;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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 org.alfresco.events.types.ActivityEvent;
import org.alfresco.events.types.Event;
import org.alfresco.model.ContentModel;
import org.alfresco.model.QuickShareModel;
import org.alfresco.repo.Client;
import org.alfresco.repo.Client.ClientType;
import org.alfresco.repo.action.executer.MailActionExecuter;
import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails;
import org.alfresco.repo.copy.CopyServicePolicies;
import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback;
import org.alfresco.repo.events.EventPreparator;
import org.alfresco.repo.events.EventPublisher;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.quickshare.ClientAppConfig.ClientApp;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.preference.PreferenceService;
import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
import org.alfresco.service.cmr.quickshare.QuickShareDTO;
import org.alfresco.service.cmr.quickshare.QuickShareDisabledException;
import org.alfresco.service.cmr.quickshare.QuickShareService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.safehaus.uuid.UUID;
import org.safehaus.uuid.UUIDGenerator;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* QuickShare Service implementation.
*
* In addition to the quick share service, this class also provides a BeforeDeleteNodePolicy and
* OnCopyNodePolicy for content with the QuickShare aspect.
*
* @author Alex Miller, janv, Jamal Kaabi-Mofrad
*/
public class QuickShareServiceImpl implements QuickShareService,
NodeServicePolicies.BeforeDeleteNodePolicy,
CopyServicePolicies.OnCopyNodePolicy,
NodeServicePolicies.OnRestoreNodePolicy
{
private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class);
static final String ATTR_KEY_SHAREDIDS_ROOT = ".sharedIds";
private static final String FTL_SHARED_NODE_URL = "shared_node_url";
private static final String FTL_SHARED_NODE_NAME = "shared_node_name";
private static final String FTL_SENDER_MESSAGE = "sender_message";
private static final String FTL_SENDER_FIRST_NAME = "sender_first_name";
private static final String FTL_SENDER_LAST_NAME = "sender_last_name";
private static final String FTL_TEMPLATE_ASSETS_URL = "template_assets_url";
private static final String DEFAULT_EMAIL_SUBJECT = "quickshare.notifier.email.subject";
private static final String EMAIL_TEMPLATE_REF ="alfresco/templates/quickshare-email-templates/quickshare-email.default.template.ftl";
private AttributeService attributeService;
private DictionaryService dictionaryService;
private NodeService nodeService;
private PermissionService permissionService;
private PersonService personService;
private PolicyComponent policyComponent;
private TenantService tenantService;
private ThumbnailService thumbnailService;
private EventPublisher eventPublisher;
private ActionService actionService;
private PreferenceService preferenceService;
/** Component to determine which behaviours are active and which not */
private BehaviourFilter behaviourFilter;
private SearchService searchService;
private SiteService siteService;
private AuthorityService authorityService;
private boolean enabled;
private String defaultEmailSender;
private ClientAppConfig clientAppConfig;
/**
* Set the attribute service
*/
public void setAttributeService(AttributeService attributeService)
{
this.attributeService = attributeService;
}
/**
* Set the dictionary service
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Set the node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Set the Permission service
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* Set the person service
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
/**
* Set the policy component
*/
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* Set the tenant service
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
/**
* Set the thumbnail service
*/
public void setThumbnailService(ThumbnailService thumbnailService)
{
this.thumbnailService = thumbnailService;
}
/**
* Set the eventPublisher
*/
public void setEventPublisher(EventPublisher eventPublisher)
{
this.eventPublisher = eventPublisher;
}
/**
* Set the actionService
*/
public void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
/**
* Set the preferenceService
*/
public void setPreferenceService(PreferenceService preferenceService)
{
this.preferenceService = preferenceService;
}
/**
* Spring configuration
*
* @param behaviourFilter the behaviourFilter to set
*/
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
{
this.behaviourFilter = behaviourFilter;
}
/**
* Spring configuration
*
* @param searchService the searchService to set
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Spring configuration
*
* @param siteService the siteService to set
*/
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
/**
* Spring configuration
*
* @param authorityService the authorityService to set
*/
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
/**
* Enable or disable this service.
*/
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
/**
* Set the default email sender
*/
public void setDefaultEmailSender(String defaultEmailSender)
{
this.defaultEmailSender = defaultEmailSender;
}
/**
* Set the quickShare clientAppConfig
*/
public void setClientAppConfig(ClientAppConfig clientAppConfig)
{
this.clientAppConfig = clientAppConfig;
}
private void checkMandatoryProperties()
{
PropertyCheck.mandatory(this, "attributeService", attributeService);
PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
PropertyCheck.mandatory(this, "nodeService", nodeService);
PropertyCheck.mandatory(this, "permissionService", permissionService);
PropertyCheck.mandatory(this, "personService", personService);
PropertyCheck.mandatory(this, "policyComponent", policyComponent);
PropertyCheck.mandatory(this, "tenantService", tenantService);
PropertyCheck.mandatory(this, "thumbnailService", thumbnailService);
PropertyCheck.mandatory(this, "eventPublisher", eventPublisher);
PropertyCheck.mandatory(this, "actionService", actionService);
PropertyCheck.mandatory(this, "preferenceService", preferenceService);
PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender);
PropertyCheck.mandatory(this, "clientAppConfig", clientAppConfig);
PropertyCheck.mandatory(this, "searchService", searchService);
PropertyCheck.mandatory(this, "siteService", siteService);
PropertyCheck.mandatory(this, "authorityService", authorityService);
}
/**
* The initialise method. Register our policies.
*/
public void init()
{
checkMandatoryProperties();
// Register interest in the beforeDeleteNode policy - note: currently for content only !!
policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
ContentModel.TYPE_CONTENT,
new JavaBehaviour(this, "beforeDeleteNode"));
//Register interest in the onCopyNodePolicy to block copying of quick share metadta
policyComponent.bindClassBehaviour(
CopyServicePolicies.OnCopyNodePolicy.QNAME,
QuickShareModel.ASPECT_QSHARE,
new JavaBehaviour(this, "getCopyCallback"));
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.OnRestoreNodePolicy.QNAME,
QuickShareModel.ASPECT_QSHARE,
new JavaBehaviour(this, "onRestoreNode"));
}
@Override
public QuickShareDTO shareContent(final NodeRef nodeRef)
{
checkEnabled();
//Check the node is the correct type
final QName typeQName = nodeService.getType(nodeRef);
if (isSharable(typeQName) == false)
{
throw new InvalidNodeRefException(nodeRef);
}
final String sharedId;
// Only add the quick share aspect if it isn't already present.
// If it is retura dto built from the existing properties.
if (! nodeService.getAspects(nodeRef).contains(QuickShareModel.ASPECT_QSHARE))
{
UUID uuid = UUIDGenerator.getInstance().generateRandomBasedUUID();
sharedId = Base64.encodeBase64URLSafeString(uuid.toByteArray()); // => 22 chars (eg. q3bEKPeDQvmJYgt4hJxOjw)
final Map<QName, Serializable> props = new HashMap<QName, Serializable>(2);
props.put(QuickShareModel.PROP_QSHARE_SHAREDID, sharedId);
props.put(QuickShareModel.PROP_QSHARE_SHAREDBY, AuthenticationUtil.getRunAsUser());
// Disable audit to preserve modifier and modified date
// see MNT-11960
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
// consumer/contributor should be able to add "shared" aspect (MNT-10366)
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
public Void doWork()
{
nodeService.addAspect(nodeRef, QuickShareModel.ASPECT_QSHARE, props);
return null;
}
});
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
final NodeRef tenantNodeRef = tenantService.getName(nodeRef);
TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>()
{
public Void doWork() throws Exception
{
attributeService.setAttribute(tenantNodeRef, ATTR_KEY_SHAREDIDS_ROOT, sharedId);
return null;
}
});
final StringBuffer sb = new StringBuffer();
sb.append("{").append("\"sharedId\":\"").append(sharedId).append("\"").append("}");
eventPublisher.publishEvent(new EventPreparator(){
@Override
public Event prepareEvent(String user, String networkId, String transactionId)
{
return new ActivityEvent("quickshare", transactionId, networkId, user, nodeRef.getId(),
null, typeQName.toString(), Client.asType(ClientType.webclient), sb.toString(),
null, null, 0l, null);
}
});
if (logger.isInfoEnabled())
{
logger.info("QuickShare - shared content: "+sharedId+" ["+nodeRef+"]");
}
}
else
{
sharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
if (logger.isDebugEnabled())
{
logger.debug("QuickShare - content already shared: "+sharedId+" ["+nodeRef+"]");
}
}
return new QuickShareDTO(sharedId);
}
/**
* Is this service enable?
* @throws QuickShareDisabledException if it isn't.
*/
private void checkEnabled()
{
if (enabled == false)
{
throw new QuickShareDisabledException("QuickShare is disabled system-wide");
}
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> getMetaData(NodeRef nodeRef)
{
// TODO This functionality MUST be available when quickshare is also disabled, therefor refactor it out from the quickshare package to a more common package.
Map<QName, Serializable> nodeProps = nodeService.getProperties(nodeRef);
ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
String modifierUserName = (String)nodeProps.get(ContentModel.PROP_MODIFIER);
Map<QName, Serializable> personProps = null;
if (modifierUserName != null)
{
try
{
NodeRef personRef = personService.getPerson(modifierUserName);
if (personRef != null)
{
personProps = nodeService.getProperties(personRef);
}
}
catch (NoSuchPersonException nspe)
{
// absorb this exception - eg. System (or maybe the user has been deleted)
if (logger.isInfoEnabled())
{
logger.info("MetaDataGet - no such person: "+modifierUserName);
}
}
}
Map<String, Object> metadata = new HashMap<String, Object>(8);
metadata.put("nodeRef", nodeRef.toString());
metadata.put("name", nodeProps.get(ContentModel.PROP_NAME));
metadata.put("title", nodeProps.get(ContentModel.PROP_TITLE));
if (contentData != null)
{
metadata.put("mimetype", contentData.getMimetype());
metadata.put("size", contentData.getSize());
}
else
{
metadata.put("size", 0L);
}
metadata.put("modified", nodeProps.get(ContentModel.PROP_MODIFIED));
if (personProps != null)
{
metadata.put("modifierFirstName", personProps.get(ContentModel.PROP_FIRSTNAME));
metadata.put("modifierLastName", personProps.get(ContentModel.PROP_LASTNAME));
}
// thumbnail defs for this nodeRef
List<String> thumbnailDefs = new ArrayList<String>(7);
if (contentData != null)
{
// Note: thumbnail defs only appear in this list if they can produce a thumbnail for the content
// found in the content property of this node. This will be determined by looking at the mimetype of the content
// and the destination mimetype of the thumbnail.
List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentData.getMimetype(), contentData.getSize());
for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions)
{
thumbnailDefs.add(thumbnailDefinition.getName());
}
}
metadata.put("thumbnailDefinitions", thumbnailDefs);
// thumbnail instances for this nodeRef
List<NodeRef> thumbnailRefs = thumbnailService.getThumbnails(nodeRef, ContentModel.PROP_CONTENT, null, null);
List<String> thumbnailNames = new ArrayList<String>(thumbnailRefs.size());
for (NodeRef thumbnailRef : thumbnailRefs)
{
thumbnailNames.add((String)nodeService.getProperty(thumbnailRef, ContentModel.PROP_NAME));
}
metadata.put("thumbnailNames", thumbnailNames);
metadata.put("lastThumbnailModificationData", (List<String>)nodeProps.get(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA));
if (nodeProps.containsKey(QuickShareModel.PROP_QSHARE_SHAREDID))
{
metadata.put("sharedId", nodeProps.get(QuickShareModel.PROP_QSHARE_SHAREDID));
}
else
{
QName type = nodeService.getType(nodeRef);
boolean sharable = isSharable(type);
metadata.put("sharable", sharable);
}
Map<String, Object> model = new HashMap<String, Object>(2);
model.put("item", metadata);
return model;
}
@Override
public Pair<String, NodeRef> getTenantNodeRefFromSharedId(final String sharedId)
{
NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork<NodeRef>()
{
public NodeRef doWork() throws Exception
{
return (NodeRef) attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
}
});
if (nodeRef == null)
{
/* TODO
* Temporary fix for RA-1093 and MNT-16224. The extra lookup should be
* removed (the same as before, just throw the 'InvalidSharedIdException' exception) when we
* have a system wide patch to remove the 'shared' aspect of the nodes that have been archived while shared.
*/
// TMDQ
final String query = "+TYPE:\"cm:content\" AND +ASPECT:\"qshare:shared\" AND =qshare:sharedId:\"" + sharedId + "\"";
SearchParameters sp = new SearchParameters();
sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
sp.setQuery(query);
sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
List<NodeRef> nodeRefs = null;
ResultSet results = null;
try
{
results = searchService.query(sp);
nodeRefs = results.getNodeRefs();
}
catch (Exception ex)
{
throw new InvalidSharedIdException(sharedId);
}
finally
{
if (results != null)
{
results.close();
}
}
if (nodeRefs.size() != 1)
{
throw new InvalidSharedIdException(sharedId);
}
nodeRef = tenantService.getName(nodeRefs.get(0));
}
// note: relies on tenant-specific (ie. mangled) nodeRef
String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
return new Pair<>(tenantDomain, tenantService.getBaseName(nodeRef));
}
@Override
public Map<String, Object> getMetaData(String sharedId)
{
checkEnabled();
Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
final String tenantDomain = pair.getFirst();
final NodeRef nodeRef = pair.getSecond();
Map<String, Object> model = TenantUtil.runAsSystemTenant(new TenantRunAsWork<Map<String, Object>>()
{
public Map<String, Object> doWork() throws Exception
{
checkQuickShareNode(nodeRef);
return getMetaData(nodeRef);
}
}, tenantDomain);
if (logger.isDebugEnabled())
{
logger.debug("QuickShare - retrieved metadata: "+sharedId+" ["+nodeRef+"]["+model+"]");
}
//model.put("nodeRef", nodeRef)
return model;
}
private void checkQuickShareNode(final NodeRef nodeRef)
{
if (! nodeService.getAspects(nodeRef).contains(QuickShareModel.ASPECT_QSHARE))
{
throw new InvalidNodeRefException(nodeRef);
}
}
// behaviour - currently registered for content only !!
// note: will remove "share" even if node is only being archived (ie. moved to trash) => a subsequent restore will *not* restore the "share"
public void beforeDeleteNode(final NodeRef beforeDeleteNodeRef)
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
String sharedId = (String)nodeService.getProperty(beforeDeleteNodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
if (sharedId != null)
{
try
{
Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
@SuppressWarnings("unused")
final String tenantDomain = pair.getFirst();
final NodeRef nodeRef = pair.getSecond();
// note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy
if (nodeRef.equals(beforeDeleteNodeRef))
{
// Disable audit to preserve modifier and modified date
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
removeSharedId(sharedId);
}
}
catch (InvalidSharedIdException ex)
{
logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef);
}
}
return null;
}
});
}
/* TODO
* Temporary fix for MNT-16224. This method should be removed when we
* have a system wide patch to remove the 'shared' aspect of the nodes that have been archived while shared.
*/
@Override
public void onRestoreNode(ChildAssociationRef childAssocRef)
{
final NodeRef childNodeRef = childAssocRef.getChildRef();
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
if (nodeService.hasAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE))
{
// Disable audit to preserve modifier and modified date
behaviourFilter.disableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
nodeService.removeAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE);
}
finally
{
behaviourFilter.enableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
return null;
}
});
}
private void removeSharedId(final String sharedId)
{
TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>()
{
public Void doWork() throws Exception
{
attributeService.removeAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
return null;
}
});
}
@Override
public void unshareContent(final String sharedId)
{
Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
final String tenantDomain = pair.getFirst();
final NodeRef nodeRef = pair.getSecond();
TenantUtil.runAsSystemTenant(new TenantRunAsWork<Void>()
{
public Void doWork() throws Exception
{
QName typeQName = nodeService.getType(nodeRef);
if (! isSharable(typeQName))
{
throw new InvalidNodeRefException(nodeRef);
}
String nodeSharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
if (! EqualsHelper.nullSafeEquals(nodeSharedId, sharedId))
{
logger.warn("SharedId mismatch: expected="+sharedId+",actual="+nodeSharedId);
}
// Disable audit to preserve modifier and modified date
// And not to create version
// see MNT-15654
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
return null;
}
}, tenantDomain);
removeSharedId(sharedId);
if (logger.isInfoEnabled())
{
logger.info("QuickShare - unshared content: "+sharedId+" ["+nodeRef+"]");
}
}
private boolean isSharable(QName type)
{
return type.equals(ContentModel.TYPE_CONTENT) || dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT);
}
// Prevent copying of Quick share properties on node copy.
@Override
public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails)
{
return DoNothingCopyBehaviourCallback.getInstance();
}
@Override
public boolean canRead(String sharedId)
{
Pair<String, NodeRef> pair = getTenantNodeRefFromSharedId(sharedId);
final String tenantDomain = pair.getFirst();
final NodeRef nodeRef = pair.getSecond();
return TenantUtil.runAsTenant(new TenantRunAsWork<Boolean>()
{
public Boolean doWork() throws Exception
{
try
{
checkQuickShareNode(nodeRef);
return permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.ALLOWED;
}
catch (AccessDeniedException ex)
{
return false;
}
}
}, tenantDomain);
}
@Override
public void sendEmailNotification(final QuickShareEmailRequest emailRequest)
{
ParameterCheck.mandatory("emailRequest", emailRequest);
emailRequest.validate();
ClientApp clientApp = clientAppConfig.getClient(emailRequest.getClient());
if (clientApp == null)
{
throw new QuickShareClientNotFoundException("Client was not found [" + emailRequest.getClient() + "]");
}
// Set the details of the person sending the email
final String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser();
final NodeRef senderNodeRef = personService.getPerson(authenticatedUser, false);
final Map<QName, Serializable> senderProps = nodeService.getProperties(senderNodeRef);
final String senderFirstName = (String) senderProps.get(ContentModel.PROP_FIRSTNAME);
final String senderLastName = (String) senderProps.get(ContentModel.PROP_LASTNAME);
final String senderFullName = ((senderFirstName != null ? senderFirstName + " " : "") + (senderLastName != null ? senderLastName : "")).trim();
// Set the default model information
Map<String, Serializable> templateModel = new HashMap<>(6);
templateModel.put(FTL_SENDER_FIRST_NAME, senderFirstName);
templateModel.put(FTL_SENDER_LAST_NAME, senderLastName);
final String sharedNodeUrl = getUrl(clientApp.getSharedLinkBaseUrl()) + '/' + emailRequest.getSharedId();
templateModel.put(FTL_SHARED_NODE_URL, sharedNodeUrl);
templateModel.put(FTL_SHARED_NODE_NAME, emailRequest.getSharedNodeName());
templateModel.put(FTL_SENDER_MESSAGE, emailRequest.getSenderMessage());
String templateAssetsUrl = getUrl(clientApp.getTemplateAssetsUrl());
templateModel.put(FTL_TEMPLATE_ASSETS_URL, templateAssetsUrl);
// Set the email details
Map<String, Serializable> actionParams = new HashMap<>();
// Email sender. By default the current-user's email address will not be used to send this mail.
// However, current-user's first and lastname will be used as the personal name.
actionParams.put(MailActionExecuter.PARAM_FROM, this.defaultEmailSender);
actionParams.put(MailActionExecuter.PARAM_FROM_PERSONAL_NAME, senderFullName);
actionParams.put(MailActionExecuter.PARAM_SUBJECT, DEFAULT_EMAIL_SUBJECT);
actionParams.put(MailActionExecuter.PARAM_SUBJECT_PARAMS, new Object[] { senderFirstName, senderLastName, emailRequest.getSharedNodeName() });
actionParams.put(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, emailRequest.isIgnoreSendFailure());
// Pick the template
actionParams.put(MailActionExecuter.PARAM_TEMPLATE, EMAIL_TEMPLATE_REF);
actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) templateModel);
actionParams.put(MailActionExecuter.PARAM_LOCALE, getDefaultIfNull(getEmailCreatorLocale(authenticatedUser), emailRequest.getLocale()));
for (String to : emailRequest.getToEmails())
{
Map<String, Serializable> params = new HashMap<>(actionParams);
params.put(MailActionExecuter.PARAM_TO, to);
Action mailAction = actionService.createAction(MailActionExecuter.NAME, params);
actionService.executeAction(mailAction, null, false, true);
}
}
@Override
public boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId)
{
boolean canDeleteSharedLink = false;
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
String siteName = getSiteName(nodeRef);
boolean isSharedByCurrentUser = currentUser.equals(sharedByUserId);
if (siteName != null)
{
// node belongs to a site - current user must be a manager or collaborator or someone who shared the link
String role = siteService.getMembersRole(siteName, currentUser);
if (isSharedByCurrentUser || (role != null && (role.equals(SiteModel.SITE_MANAGER) || role.equals(SiteModel.SITE_COLLABORATOR))))
{
canDeleteSharedLink = true;
}
}
else if (isSharedByCurrentUser || (authorityService.isAdminAuthority(currentUser)))
{
// node does not belongs to a site - current user must be the person who shared the link or an admin
canDeleteSharedLink = true;
}
return canDeleteSharedLink;
}
private String getSiteName(NodeRef nodeRef)
{
NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE))
{
// check that we can read parent name
String parentName = (String) nodeService.getProperty(parent, ContentModel.PROP_NAME);
if (nodeService.getPrimaryParent(nodeRef) != null)
{
parent = nodeService.getPrimaryParent(parent).getParentRef();
}
}
if (parent == null)
{
return null;
}
return nodeService.getProperty(parent, ContentModel.PROP_NAME).toString();
}
private String getUrl(String url)
{
if (url.endsWith("/"))
{
return url.substring(0, url.length() - 1);
}
return url;
}
private <T> T getDefaultIfNull(T defaultValue, T newValue)
{
return (newValue == null) ? defaultValue : newValue;
}
private Locale getEmailCreatorLocale(String userId)
{
String localeString = (String) preferenceService.getPreference(userId, "locale");
return I18NUtil.parseLocale(localeString);
}
/**
* Represents an email request to send a quick share link.
*/
public static class QuickShareEmailRequest
{
/**
* The client's name that must be registered in order to send emails.
*/
private String client;
/**
* Optional Locale for subject and body text
*/
private Locale locale;
/**
* The email addresses (1 or many) of the recipients.
*/
private Set<String> toEmails;
/**
* The shared id.
*/
private String sharedId;
/**
* The shared content name.
*/
private String sharedNodeName;
/**
* Optional message from the sender.
*/
private String senderMessage;
/**
* Whether to ignore throwing exception or not. The default is false.
*/
private boolean ignoreSendFailure = false;
public void validate()
{
ParameterCheck.mandatoryCollection("toEmails", toEmails);
ParameterCheck.mandatoryString("sharedId", sharedId);
ParameterCheck.mandatoryString("sharedNodeName", sharedNodeName);
}
/**
* {@link QuickShareEmailRequest#locale}
*/
public Locale getLocale()
{
return this.locale;
}
/**
* {@link QuickShareEmailRequest#locale}
*/
public void setLocale(Locale locale)
{
this.locale = locale;
}
/**
* {@link QuickShareEmailRequest#toEmails}
*/
public Set<String> getToEmails()
{
return this.toEmails;
}
/**
* {@link QuickShareEmailRequest#toEmails}
*/
public void setToEmails(Collection<String> toEmails)
{
if (toEmails != null)
{
this.toEmails = Collections.unmodifiableSet(new HashSet<>(toEmails));
}
}
/**
* {@link QuickShareEmailRequest#client}
*/
public String getClient()
{
return client;
}
/**
* {@link QuickShareEmailRequest#client}
*/
public QuickShareEmailRequest setClient(String client)
{
this.client = client;
return this;
}
/**
* {@link QuickShareEmailRequest#sharedId}
*/
public String getSharedId()
{
return sharedId;
}
/**
* {@link QuickShareEmailRequest#sharedId}
*/
public QuickShareEmailRequest setSharedId(String sharedId)
{
this.sharedId = sharedId;
return this;
}
/**
* {@link QuickShareEmailRequest#sharedNodeName}
*/
public String getSharedNodeName()
{
return sharedNodeName;
}
/**
* {@link QuickShareEmailRequest#sharedNodeName}
*/
public void setSharedNodeName(String sharedNodeName)
{
this.sharedNodeName = sharedNodeName;
}
/**
* {@link QuickShareEmailRequest#senderMessage}
*/
public String getSenderMessage()
{
return senderMessage;
}
/**
* {@link QuickShareEmailRequest#senderMessage}
*/
public void setSenderMessage(String senderMessage)
{
this.senderMessage = senderMessage;
}
/**
* {@link QuickShareEmailRequest#ignoreSendFailure}
*/
public boolean isIgnoreSendFailure()
{
return ignoreSendFailure;
}
/**
* {@link QuickShareEmailRequest#ignoreSendFailure}
*/
public void setIgnoreSendFailure(Boolean ignoreSendFailure)
{
if (ignoreSendFailure != null)
{
this.ignoreSendFailure = ignoreSendFailure;
}
}
}
}