/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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 .
*/
package org.alfresco.repo.site;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.query.CannedQuery;
import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryPageDetails;
import org.alfresco.query.CannedQueryParameters;
import org.alfresco.query.CannedQueryResults;
import org.alfresco.query.CannedQuerySortDetails;
import org.alfresco.query.CannedQuerySortDetails.SortOrder;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.activities.ActivityType;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy;
import org.alfresco.repo.node.getchildren.FilterProp;
import org.alfresco.repo.node.getchildren.FilterPropString;
import org.alfresco.repo.node.getchildren.FilterPropString.FilterTypeString;
import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery;
import org.alfresco.repo.node.getchildren.GetChildrenCannedQueryFactory;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser;
import org.alfresco.repo.security.authentication.AuthenticationContext;
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.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.preference.PreferenceService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
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.LimitBy;
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.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityService.AuthorityFilter;
import org.alfresco.service.cmr.security.AuthorityType;
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.security.PublicServiceAccessService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteMemberInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.PropertyMap;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.extensions.surf.util.ParameterCheck;
/**
* Site Service Implementation. Also bootstraps the site AVM and DM stores.
*
* @author Roy Wetherall
*/
public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServiceInternal, SiteModel, NodeServicePolicies.OnRestoreNodePolicy
{
/** Logger */
protected static Log logger = LogFactory.getLog(SiteServiceImpl.class);
/** The DM store where site's are kept */
public static final StoreRef SITE_STORE = new StoreRef("workspace://SpacesStore");
/** Activity tool */
private static final String ACTIVITY_TOOL = "siteService";
private static final String SITE_PREFIX = "site_";
private static final String GROUP_SITE_PREFIX = PermissionService.GROUP_PREFIX + SITE_PREFIX;
private static final int GROUP_PREFIX_LENGTH = PermissionService.GROUP_PREFIX.length();
private static final int GROUP_SITE_PREFIX_LENGTH = GROUP_SITE_PREFIX.length();
// note: caches are tenant-aware (if using EhCacheAdapter shared cache)
private SimpleCache singletonCache; // eg. for siteHomeNodeRef
private final String KEY_SITEHOME_NODEREF = "key.sitehome.noderef";
private SimpleCache siteNodeRefCache; // for site shortname to nodeRef lookup
private String sitesXPath;
/** Messages */
private static final String MSG_UNABLE_TO_CREATE = "site_service.unable_to_create";
private static final String MSG_SITE_SHORT_NAME_TOO_LONG = "site_service.short_name_too_long";
private static final String MSG_VISIBILITY_GROUP_MISSING = "site_service.visibility_group_missing";
private static final String MSG_CAN_NOT_UPDATE = "site_service.can_not_update";
private static final String MSG_CAN_NOT_DELETE = "site_service.can_not_delete";
private static final String MSG_CAN_NOT_REMOVE_MSHIP = "site_service.can_not_remove_membership";
private static final String MSG_DO_NOT_CHANGE_MGR = "site_service.do_not_change_manager";
private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_membership";
private static final String MSG_SITE_CONTAINER_NOT_FOLDER = "site_service.site_container_not_folder";
private static final String MSG_INVALID_SITE_TYPE = "site_service.invalid_site_type";
/* Services */
private NodeService nodeService;
private NodeService directNodeService;
private FileFolderService fileFolderService;
private SearchService searchService;
private NamespaceService namespaceService;
private PreferenceService preferenceService;
private PermissionService permissionService;
private ActivityService activityService;
private PersonService personService;
private AuthenticationContext authenticationContext;
private TaggingService taggingService;
private AuthorityService authorityService;
private DictionaryService dictionaryService;
private TenantService tenantService;
private RetryingTransactionHelper retryingTransactionHelper;
private Comparator roleComparator;
private SysAdminParams sysAdminParams;
private BehaviourFilter behaviourFilter;
private SitesPermissionCleaner sitesPermissionsCleaner;
private PolicyComponent policyComponent;
private PublicServiceAccessService publicServiceAccessService;
private NamedObjectRegistry> cannedQueryRegistry;
/**
* Set the path to the location of the sites root folder. For example:
*
* ./app:company_home/st:sites
*
* @param sitesXPath a valid XPath
*/
public void setSitesXPath(String sitesXPath)
{
this.sitesXPath = sitesXPath;
}
public void setPreferenceService(PreferenceService preferenceService)
{
this.preferenceService = preferenceService;
}
/**
* Set node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Set the unprotected node service
*/
public void setDirectNodeService(NodeService directNodeService)
{
this.directNodeService = directNodeService;
}
/**
* Set file folder service
*/
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
/**
* Set search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set Namespace service
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* Set permission service
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* Set activity service
*/
public void setActivityService(ActivityService activityService)
{
this.activityService = activityService;
}
/**
* Set person service
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
/**
* Set authentication component
*/
public void setAuthenticationContext(
AuthenticationContext authenticationContext)
{
this.authenticationContext = authenticationContext;
}
/**
* Set the tagging service
*/
public void setTaggingService(TaggingService taggingService)
{
this.taggingService = taggingService;
}
/**
* Set the authority service
*/
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
/**
* Set the dictionary service
*
* @param dictionaryService dictionary service
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Set the tenant service
*
* @param tenantService tenant service
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setSingletonCache(SimpleCache singletonCache)
{
this.singletonCache = singletonCache;
}
public void setSiteNodeRefCache(SimpleCache siteNodeRefCache)
{
this.siteNodeRefCache = siteNodeRefCache;
}
/**
* Sets helper that provides transaction callbacks
*/
public void setTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
{
this.retryingTransactionHelper = retryingTransactionHelper;
}
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
public void setRoleComparator(Comparator roleComparator)
{
this.roleComparator = roleComparator;
}
public void setSysAdminParams(SysAdminParams sysAdminParams)
{
this.sysAdminParams = sysAdminParams;
}
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
{
this.behaviourFilter = behaviourFilter;
}
public void setSitesPermissionsCleaner(SitesPermissionCleaner sitesPermissionsCleaner)
{
this.sitesPermissionsCleaner = sitesPermissionsCleaner;
}
public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService)
{
this.publicServiceAccessService = publicServiceAccessService;
}
/**
* Set the registry of {@link CannedQueryFactory canned queries}
*/
public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry)
{
this.cannedQueryRegistry = cannedQueryRegistry;
}
public Comparator getRoleComparator()
{
return roleComparator;
}
/**
* Checks that all necessary properties and services have been provided.
*/
public void init()
{
PropertyCheck.mandatory(this, "nodeService", nodeService);
PropertyCheck.mandatory(this, "directNodeService", directNodeService);
PropertyCheck.mandatory(this, "fileFolderService", fileFolderService);
PropertyCheck.mandatory(this, "searchService", searchService);
PropertyCheck.mandatory(this, "namespaceService", namespaceService);
PropertyCheck.mandatory(this, "permissionService", permissionService);
PropertyCheck.mandatory(this, "authenticationContext", authenticationContext);
PropertyCheck.mandatory(this, "personService", personService);
PropertyCheck.mandatory(this, "activityService", activityService);
PropertyCheck.mandatory(this, "taggingService", taggingService);
PropertyCheck.mandatory(this, "authorityService", authorityService);
PropertyCheck.mandatory(this, "sitesXPath", sitesXPath);
}
/* (non-Javadoc)
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onBootstrap(ApplicationEvent event)
{
this.policyComponent.bindClassBehaviour(
OnRestoreNodePolicy.QNAME,
SiteModel.TYPE_SITE,
new JavaBehaviour(this, "onRestoreNode"));
}
/* (non-Javadoc)
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent)
*/
@Override
protected void onShutdown(ApplicationEvent event)
{
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.site.SiteService#hasCreateSitePermissions()
*/
public boolean hasCreateSitePermissions()
{
// NOTE: see ALF-13580 - since 3.4.6 PermissionService.CONTRIBUTOR is no longer used as the default on the Sites folder
// instead the ability to call createSite() and the Spring configured ACL is the mechanism used to protect access.
return (publicServiceAccessService.hasAccess("SiteService", "createSite", "", "", "", "", true) == AccessStatus.ALLOWED);
}
/**
* @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
*/
public SiteInfo createSite(final String sitePreset,
String passedShortName,
final String title,
final String description,
final boolean isPublic)
{
// Determine the site visibility
SiteVisibility visibility = SiteVisibility.PRIVATE;
if (isPublic == true)
{
visibility = SiteVisibility.PUBLIC;
}
// Create the site
return createSite(sitePreset, passedShortName, title, description, visibility);
}
/**
* @see org.alfresco.service.cmr.site.SiteService#createSite(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
*/
public SiteInfo createSite(final String sitePreset,
String passedShortName,
final String title,
final String description,
final SiteVisibility visibility)
{
return createSite(sitePreset, passedShortName, title, description, visibility, SiteModel.TYPE_SITE);
}
public SiteInfo createSite(final String sitePreset,
String passedShortName,
final String title,
final String description,
final SiteVisibility visibility,
final QName siteType)
{
// Check that the provided site type is a subtype of TYPE_SITE
if (SiteModel.TYPE_SITE.equals(siteType) == false &&
dictionaryService.isSubClass(siteType, TYPE_SITE) == false)
{
throw new SiteServiceException(MSG_INVALID_SITE_TYPE, new Object[]{siteType});
}
// Remove spaces from shortName
final String shortName = passedShortName.replaceAll(" ", "");
// Check to see if we already have a site of this name
NodeRef existingSite = getSiteNodeRef(shortName, false);
if (existingSite != null)
{
// Throw an exception since we have a duplicate site name
throw new SiteServiceException(MSG_UNABLE_TO_CREATE, new Object[]{shortName});
}
// Check that the site name isn't too long
// Authorities are limited to 100 characters by the PermissionService
int longestPermissionLength = 0;
for (String permission : permissionService.getSettablePermissions(siteType))
{
if (permission.length() > longestPermissionLength)
longestPermissionLength = permission.length();
}
int maximumPermisionGroupLength = 99 - longestPermissionLength;
if (getSiteGroup(shortName, true).length() > maximumPermisionGroupLength)
{
throw new SiteServiceException(MSG_SITE_SHORT_NAME_TOO_LONG, new Object[] {
shortName, maximumPermisionGroupLength - getSiteGroup("", true).length()
});
}
// Get the site parent node reference
final NodeRef siteParent = getSiteParent(shortName);
if (siteParent == null)
{
throw new SiteServiceException("No root sites folder exists");
}
// Create the site node
final PropertyMap properties = new PropertyMap(4);
properties.put(ContentModel.PROP_NAME, shortName);
properties.put(SiteModel.PROP_SITE_PRESET, sitePreset);
properties.put(SiteModel.PROP_SITE_VISIBILITY, visibility.toString());
properties.put(ContentModel.PROP_TITLE, title);
properties.put(ContentModel.PROP_DESCRIPTION, description);
final NodeRef siteNodeRef = AuthenticationUtil.runAs(new RunAsWork() {
@Override
public NodeRef doWork() throws Exception {
behaviourFilter.disableBehaviour(siteParent, ContentModel.ASPECT_AUDITABLE);
try
{
return nodeService.createNode(
siteParent,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, shortName),
siteType,
properties
).getChildRef();
}
finally
{
behaviourFilter.enableBehaviour(siteParent, ContentModel.ASPECT_AUDITABLE);
}
}
}, AuthenticationUtil.getSystemUserName());
// Make the new site a tag scope
this.taggingService.addTagScope(siteNodeRef);
// Clear the sites inherited permissions
this.permissionService.setInheritParentPermissions(siteNodeRef, false);
// Create the relevant groups and assign permissions
setupSitePermissions(siteNodeRef, shortName, visibility, null);
// Return created site information
Map customProperties = getSiteCustomProperties(siteNodeRef);
SiteInfo siteInfo = new SiteInfoImpl(sitePreset, shortName, title, description, visibility, customProperties, siteNodeRef);
return siteInfo;
}
/**
* Setup the Site permissions.
*
* Creates the top-level site group, plus all the Role groups required for users of the site.
*
* Note - Changes here likely need to be replicated to the {@link #updateSite(SiteInfo)}
* method too, as that also has to deal with Site Permissions.
*
* @param siteNodeRef
* @param shortName
* @param visibility
*/
private void setupSitePermissions(
final NodeRef siteNodeRef, final String shortName, final SiteVisibility visibility, final Map> memberships)
{
// Get the current user
final String currentUser = authenticationContext.getCurrentUserName();
// Create the relevant groups and assign permissions
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork