diff --git a/config/alfresco/activiti-context.xml b/config/alfresco/activiti-context.xml index 22686656ec..82398e88c7 100644 --- a/config/alfresco/activiti-context.xml +++ b/config/alfresco/activiti-context.xml @@ -64,6 +64,8 @@ + + diff --git a/config/alfresco/comment-services-context.xml b/config/alfresco/comment-services-context.xml index 33977de89a..ffaf07b707 100644 --- a/config/alfresco/comment-services-context.xml +++ b/config/alfresco/comment-services-context.xml @@ -2,6 +2,24 @@ + + + + + + + + + + + + + + + + + + @@ -34,10 +52,32 @@ + + + + + + + + + org.alfresco.repo.forum.CommentService.listComments=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties + + + + + + + + + + + + + diff --git a/config/alfresco/model/bpmModel.xml b/config/alfresco/model/bpmModel.xml index 78cb147d52..bef305f030 100644 --- a/config/alfresco/model/bpmModel.xml +++ b/config/alfresco/model/bpmModel.xml @@ -260,7 +260,7 @@ bpm:workflowPackage - true + false false diff --git a/config/alfresco/opencmis-context.xml b/config/alfresco/opencmis-context.xml index aab15f62bb..ff9c5d1dae 100644 --- a/config/alfresco/opencmis-context.xml +++ b/config/alfresco/opencmis-context.xml @@ -2,7 +2,6 @@ - @@ -79,7 +78,10 @@ - + + + + @@ -111,6 +113,7 @@ + @@ -152,5 +155,5 @@ - + \ No newline at end of file diff --git a/config/alfresco/preference-service-context.xml b/config/alfresco/preference-service-context.xml index abdff02770..ea255b4ae3 100644 --- a/config/alfresco/preference-service-context.xml +++ b/config/alfresco/preference-service-context.xml @@ -44,6 +44,7 @@ + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 8ae53c972d..37b62593c2 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -968,8 +968,10 @@ org.alfresco.service.cmr.site.SiteService.deleteSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.findSites=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getContainer=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties + org.alfresco.service.cmr.site.SiteService.listContainers=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getMembersRole=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getMembersRoleInfo=ACL_ALLOW + org.alfresco.service.cmr.site.SiteService.resolveSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSite=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getSiteGroup=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSiteRoleGroup=ACL_ALLOW @@ -980,8 +982,11 @@ org.alfresco.service.cmr.site.SiteService.isMember=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembers=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembersInfo=ACL_ALLOW + org.alfresco.service.cmr.site.SiteService.listMembersPaged=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listSites=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties + org.alfresco.service.cmr.site.SiteService.listSitesPaged=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.removeMembership=ACL_ALLOW + org.alfresco.service.cmr.site.SiteService.canAddMember=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.setMembership=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.updateSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.*=ACL_DENY diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 4c571b0117..18904ea076 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -71,6 +71,7 @@ system.workflow.engine.jbpm.enabled=true # Determines if the Activiti engine is enabled system.workflow.engine.activiti.enabled=true +system.workflow.engine.activiti.idblocksize=100 # The maximum number of groups to check for pooled tasks. For performance # reasons, this is limited to 100 by default. @@ -874,7 +875,7 @@ solr.solrUser=solr solr.solrPassword=solr # none, https solr.secureComms=https - +solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY solr.max.total.connections=40 solr.max.host.connections=40 @@ -1020,3 +1021,19 @@ trashcan.MaxSize=1000 # authority.useBridgeTable=true +# Oubound Mail +mail.service.corePoolSize=8 +mail.service.maximumPoolSize=20 + +# OpenCMIS + +# URL generation overrides + +# if true, the context path of OpenCMIS generated urls will be set to "opencmis.context.value", otherwise it will be taken from the request url +opencmis.context.override=false +opencmis.context.value= +# if true, the servlet path of OpenCMIS generated urls will be set to "opencmis.servletpath.value", otherwise it will be taken from the request url +opencmis.servletpath.override=false +opencmis.servletpath.value= +opencmis.server.override=false +opencmis.server.value= \ No newline at end of file diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index c5d0751f00..f46e42608d 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -21,7 +21,7 @@ - + @@ -33,6 +33,7 @@ hasCreateSitePermissions findSites listSites + listSitesPaged getSite listMembers listMembersInfo @@ -44,6 +45,11 @@ getSiteGroup getSiteRoleGroup getSiteRoot + canAddMember + listContainers + isFavouriteSite + getFavouriteSites + addFavouriteSite @@ -130,7 +136,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -184,4 +215,5 @@ + diff --git a/config/alfresco/subsystems/Search/solr/solr-search-context.xml b/config/alfresco/subsystems/Search/solr/solr-search-context.xml index f6f22a322a..3f967f93da 100644 --- a/config/alfresco/subsystems/Search/solr/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr/solr-search-context.xml @@ -36,6 +36,7 @@ + diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml index e6faa1b2f8..ead56c780b 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP-context.xml @@ -13,7 +13,7 @@ ${mail.port} - + ${mail.protocol} @@ -26,7 +26,7 @@ ${mail.encoding} - + ${mail.smtp.auth} ${mail.smtp.debug} @@ -40,45 +40,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - ${mail.header} - + + + + + + + + + + + + + + + + + + + + + + + + ${mail.header} + ${mail.validate.addresses} - - ${mail.from.default} - - + + ${mail.from.default} + + ${mail.from.enabled} - - - + + + ${mail.testmessage.send} @@ -98,7 +97,36 @@ - + + + + + + mailAsyncAction + + + ${mail.service.corePoolSize} + + + ${mail.service.maximumPoolSize} + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 222509d785..87d1ccdd1d 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -46,6 +46,7 @@ + - - - - - - diff --git a/source/java/org/alfresco/cmis/mapping/ObjectIdProperty.java b/source/java/org/alfresco/cmis/mapping/ObjectIdProperty.java index 0266b9550c..35264413e9 100644 --- a/source/java/org/alfresco/cmis/mapping/ObjectIdProperty.java +++ b/source/java/org/alfresco/cmis/mapping/ObjectIdProperty.java @@ -36,11 +36,11 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.BooleanClause.Occur; /** * Get the CMIS object id property. diff --git a/source/java/org/alfresco/cmis/mapping/ParentProperty.java b/source/java/org/alfresco/cmis/mapping/ParentProperty.java index fef7081a26..55a1ba2adc 100644 --- a/source/java/org/alfresco/cmis/mapping/ParentProperty.java +++ b/source/java/org/alfresco/cmis/mapping/ParentProperty.java @@ -33,11 +33,11 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.BooleanClause.Occur; /** * Get the CMIS parent property diff --git a/source/java/org/alfresco/jcr/session/SessionImpl.java b/source/java/org/alfresco/jcr/session/SessionImpl.java index b206aaaf4f..b7b8b014f6 100644 --- a/source/java/org/alfresco/jcr/session/SessionImpl.java +++ b/source/java/org/alfresco/jcr/session/SessionImpl.java @@ -86,6 +86,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.view.ExporterCrawlerParameters; import org.alfresco.service.cmr.view.ExporterService; import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.NamespacePrefixResolver; @@ -829,41 +830,35 @@ public class SessionImpl implements Session } } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getUUIDBinding() - */ + @Override public UUID_BINDING getUUIDBinding() { return uuidBinding; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getValue(java.lang.String) - */ + @Override public String getValue(String key) { return null; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#searchWithinTransaction() - */ + @Override public boolean allowReferenceWithinTransaction() { return false; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getExcludedClasses() - */ + @Override public QName[] getExcludedClasses() { return null; } + + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } } // diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index e2d050f266..de0e0550eb 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -247,7 +247,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { // Put these resources on the transactions context = null; - authentication = null; nodeInfoMap.clear(); objectInfoMap.clear(); } @@ -575,13 +574,25 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { try { + if(connector.filter(child.getNodeRef())) + { + continue; + } + // create a child CMIS object CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); + if (getObjectInfo(repositoryId, ni.getObjectId())==null) { // ignore invalid children continue; } + + if (CMISObjectVariant.NOT_A_CMIS_OBJECT.equals(ni.getObjectVariant())) + { + continue; //Skip non-cmis objects + } + ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions, includeRelationships, renditionFilter, false, false); @@ -700,12 +711,22 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { continue; } - + if(isFolder && type.getAlfrescoClass().equals(ContentModel.TYPE_SYSTEM_FOLDER)) { continue; } + + if(connector.isHidden(child.getChildRef())) + { + continue; + } + if(connector.filter(child.getChildRef())) + { + continue; + } + // create a child CMIS object ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); CMISNodeInfo ni = createNodeInfo(child.getChildRef()); @@ -982,6 +1003,12 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { for (NodeRef nodeRef : nodeRefs) { + // TODO - perhaps filter by path in the query instead? + if(connector.filter(nodeRef)) + { + continue; + } + if (skipCounter > 0) { skipCounter--; @@ -1117,7 +1144,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.getActivityPoster().postFileFolderAdded(fileInfo); - return nodeRef.toString(); + return nodeRef.getId(); } private String stripEncoding(String mimeType) @@ -1680,7 +1707,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr } catch (Exception e) { - result.add(nodeRef.toString()); + result.add(nodeRef.getId()); } return result; @@ -1735,6 +1762,11 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr rootNodeRef, Arrays.asList(path.substring(1).split("/"))); + if(connector.filter(fileInfo.getNodeRef())) + { + throw new CmisObjectNotFoundException("Object not found: " + path); + } + CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef()); ObjectData object = connector.createCMISObject( @@ -1992,6 +2024,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr NodeRef nodeRef = info.getNodeRef(); VersionHistory versionHistory = ((CMISNodeInfoImpl) info).getVersionHistory(); +// Collection versions = versionHistory.getAllVersions(); +// if (versionHistory == null || versions.size() == 0 || versions.size() == 1 && versions.contains(versionHistory.getHeadVersion())) if (versionHistory == null) { // add current version @@ -2427,6 +2461,19 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr return info; } + protected String getGuid(String nodeId) + { + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } + } + /** * Collects the {@link ObjectInfo} about an object. * @@ -2530,7 +2577,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr info.setWorkingCopyId(null); info.setWorkingCopyOriginalId(null); - info.setVersionSeriesId(ni.getCurrentNodeId()); + info.setVersionSeriesId(getGuid(ni.getCurrentNodeId())); if (ni.isPWC()) { @@ -2544,7 +2591,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if (ni.hasPWC()) { - info.setWorkingCopyId(ni.getCurrentNodeId() + CMISConnector.ID_SEPERATOR + info.setWorkingCopyId(getGuid(ni.getCurrentNodeId()) + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL); info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); } else diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java.copy b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java.copy new file mode 100644 index 0000000000..da0c8cadac --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java.copy @@ -0,0 +1,2824 @@ +/* + * Copyright (C) 2005-2012 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.opencmis; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import net.sf.acegisecurity.Authentication; + +import org.alfresco.cmis.CMISInvalidArgumentException; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.CMISNodeInfo; +import org.alfresco.opencmis.dictionary.CMISObjectVariant; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.Authorization; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.search.QueryParameterDefinition; +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.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.ExtensionsData; +import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +import org.apache.chemistry.opencmis.commons.data.ObjectParentData; +import org.apache.chemistry.opencmis.commons.data.Properties; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl; +import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService; +import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; +import org.apache.chemistry.opencmis.commons.impl.server.RenditionInfoImpl; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.ObjectInfo; +import org.apache.chemistry.opencmis.commons.server.RenditionInfo; +import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * OpenCMIS service implementation + * + * @author florian.mueller + * @author Derek Hulley + * @since 4.0 + */ +public class AlfrescoCmisServiceImpl extends AbstractCmisService implements AlfrescoCmisService +{ + private static Log logger = LogFactory.getLog(AlfrescoCmisService.class); + + private static final QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); + private static final QName PARAM_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "username"); + private static final String LUCENE_QUERY_CHECKEDOUT = "+@cm\\:workingCopyOwner:${cm:username}"; + private static final String LUCENE_QUERY_CHECKEDOUT_IN_FOLDER = "+@cm\\:workingCopyOwner:${cm:username} +PARENT:\"${cm:parent}\""; + + private static final String MIN_FILTER = "cmis:name,cmis:baseTypeId,cmis:objectTypeId," + + "cmis:createdBy,cmis:creationDate,cmis:lastModifiedBy,cmis:lastModificationDate," + + "cmis:contentStreamLength,cmis:contentStreamMimeType,cmis:contentStreamFileName," + + "cmis:contentStreamId"; + + private CMISConnector connector; + private CallContext context; + private UserTransaction txn; + + private Map nodeInfoMap; + private Map objectInfoMap; + + public AlfrescoCmisService(CMISConnector connector) + { + this.connector = connector; + nodeInfoMap = new HashMap(); + objectInfoMap = new HashMap(); + } + + + public void beginCall(CallContext context) + { + this.context = context; + + AuthenticationUtil.pushAuthentication(); + + try + { + String currentUser = connector.getAuthenticationService().getCurrentUserName(); + String user = context.getUsername(); + String password = context.getPassword(); + + if (currentUser == null) + { + Authorization auth = new Authorization(user, password); + + if (auth.isTicket()) + { + connector.getAuthenticationService().validate(auth.getTicket()); + } else + { + connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); + } + + } else if (currentUser.equals(connector.getProxyUser())) + { + if (user != null && user.length() > 0) + { + AuthenticationUtil.setFullyAuthenticatedUser(user); + } + } + } catch (AuthenticationException ae) + { + throw new CmisPermissionDeniedException(ae.getMessage(), ae); + } + + // start read-only transaction + try + { + beginReadOnlyTransaction(); + } catch (Exception e) + { + AuthenticationUtil.popAuthentication(); + + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } else + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } + } + + @Override + public void close() + { + try + { + endReadOnlyTransaction(); + } catch (Exception e) + { + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } else + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } finally + { + AuthenticationUtil.popAuthentication(); + context = null; + nodeInfoMap.clear(); + objectInfoMap.clear(); + } + } + + /** + * Called directly before any CMIS method is used + */ + void beforeCall(); + + /** + * Called directly after any CMIS method is used + */ + void afterCall(); + + /** + * Call before the work method and forms the opposite of {@link #close()}. + * Gathers the type descendants tree. + */ + private TypeDefinitionContainer getTypesDescendants(int depth, TypeDefinitionWrapper tdw, + boolean includePropertyDefinitions) + { + TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl(); + + result.setTypeDefinition(tdw.getTypeDefinition(includePropertyDefinitions)); + + if (depth != 0) + { + if (tdw.getChildren() != null) + { + result.setChildren(new ArrayList()); + for (TypeDefinitionWrapper tdc : tdw.getChildren()) + { + result.getChildren().add( + getTypesDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions)); + } + } + } + + return result; + } + + // --- navigation service --- + + /* + * Lucene based getChildren - deactivated + */ + public ObjectInFolderList XgetChildren(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + // get the children references + NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + + // lucene part + QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); + DataTypeDefinition nodeRefDataType = connector.getDictionaryService().getDataType(DataTypeDefinition.NODE_REF); + + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + params.addStore(folderNodeRef.getStoreRef()); + QueryParameterDefinition parentDef = new QueryParameterDefImpl(PARAM_PARENT, nodeRefDataType, true, + folderNodeRef.toString()); + params.addQueryParameterDefinition(parentDef); + + // Build a query for the appropriate types + StringBuilder query = new StringBuilder(1024).append("+PARENT:\"${cm:parent}\" -ASPECT:\"") + .append(ContentModel.ASPECT_WORKING_COPY).append("\" +TYPE:("); + + // Include doc type if necessary + query.append('"').append(ContentModel.TYPE_CONTENT).append('"'); + query.append(" "); + query.append('"').append(ContentModel.TYPE_FOLDER).append('"'); + + // Always exclude system folders + query.append(") -TYPE:\"").append(ContentModel.TYPE_SYSTEM_FOLDER).append("\""); + params.setQuery(query.toString()); + // parseOrderBy(orderBy, params); + ResultSet resultSet = null; + + List childrenList; + try + { + resultSet = connector.getSearchService().query(params); + childrenList = resultSet.getNodeRefs(); + } finally + { + if (resultSet != null) + resultSet.close(); + } + + if (max > 0) + { + int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + NodeRef child = childrenList.get(i); + CMISNodeInfo ni = createNodeInfo(child); + + // create a child CMIS object + ObjectData object = connector.createCMISObject(ni, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); + childData.setObject(object); + + // include path segment + if (includePathSegment) + { + childData.setPathSegment(connector.getName(child)); + } + + // add it + list.add(childData); + } + } + + result.setHasMoreItems(childrenList.size() - skip > result.getObjects().size()); + result.setNumItems(BigInteger.valueOf(childrenList.size())); + + return result; + } + + public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + long start = System.currentTimeMillis(); + + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + // get the children references + NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + + // convert orderBy to sortProps + List> sortProps = null; + if (orderBy != null) + { + sortProps = new ArrayList>(1); + + String[] parts = orderBy.split(","); + int len = parts.length; + final int origLen = len; + + if (origLen > 0) + { + int maxSortProps = GetChildrenCannedQuery.MAX_FILTER_SORT_PROPS; + if (len > maxSortProps) + { + if (logger.isDebugEnabled()) + { + logger.debug("Too many sort properties in 'orderBy' - ignore those above max (max=" + + maxSortProps + ",actual=" + len + ")"); + } + len = maxSortProps; + } + for (int i = 0; i < len; i++) + { + String[] sort = parts[i].split(" +"); + + if (sort.length > 0) + { + PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService() + .findPropertyByQueryName(sort[0]); + if (propDef != null) + { + QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); + if (sortProp != null) + { + boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); + sortProps.add(new Pair(sortProp, sortAsc)); + } else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); + } + } + } else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); + } + } + } + } + } + + if (sortProps.size() < origLen) + { + logger.warn("Sort properties trimmed - either too many and/or not found: \n" + " orig: " + orderBy + + "\n" + " final: " + sortProps); + } + } + + PagingRequest pageRequest = new PagingRequest(skip, max, null); + pageRequest.setRequestTotalCountMax(skip + 10000); // TODO make this + // optional/configurable + // - affects whether + // numItems may be + // returned + + PagingResults pageOfNodeInfos = connector.getFileFolderService().list(folderNodeRef, true, true, + null, sortProps, pageRequest); + + if (max > 0) + { + for (FileInfo child : pageOfNodeInfos.getPage()) + { + try + { + // create a child CMIS object + CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); + ObjectData object = connector.createCMISObject(ni, child, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); + childData.setObject(object); + + // include path segment + if (includePathSegment) + { + childData.setPathSegment(child.getName()); + } + + // add it + list.add(childData); + } catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + // has more ? + result.setHasMoreItems(pageOfNodeInfos.hasMoreItems()); + + // total count ? + Pair totalCounts = pageOfNodeInfos.getTotalResultCount(); + if (totalCounts != null) + { + Integer totalCountLower = totalCounts.getFirst(); + Integer totalCountUpper = totalCounts.getSecond(); + if ((totalCountLower != null) && (totalCountLower.equals(totalCountUpper))) + { + result.setNumItems(BigInteger.valueOf(totalCountLower)); + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("getChildren: " + list.size() + " in " + (System.currentTimeMillis() - start) + " msecs"); + } + + return result; + } + + @Override + public List getDescendants(String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree(repositoryId, getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), depth.intValue(), + filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false, + result); + + return result; + } + + @Override + public List getFolderTree(String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree(repositoryId, getOrCreateFolderInfo(folderId, "Folder").getNodeRef(), depth.intValue(), + filter, includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true, + result); + + return result; + } + + private void getDescendantsTree(String repositoryId, NodeRef folderNodeRef, int depth, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, boolean foldersOnly, List list) + { + // get the children references + List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); + for (ChildAssociationRef child : childrenList) + { + try + { + TypeDefinitionWrapper type = connector.getType(child.getChildRef()); + if (type == null) + { + continue; + } + + boolean isFolder = (type instanceof FolderTypeDefintionWrapper); + + if (foldersOnly && !isFolder) + { + continue; + } + + if(connector.isHidden(child.getChildRef())) + { + continue; + } + + // create a child CMIS object + ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); + CMISNodeInfo ni = createNodeInfo(child.getChildRef()); + object.setObject(connector.createCMISObject(ni, filter, includeAllowableActions, includeRelationships, + renditionFilter, false, false)); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + if (includePathSegment) + { + object.setPathSegment(connector.getName(child.getChildRef())); + } + + // create the container + ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(); + container.setObject(object); + + if ((depth != 1) && isFolder) + { + container.setChildren(new ArrayList()); + getDescendantsTree(repositoryId, child.getChildRef(), depth - 1, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePathSegment, foldersOnly, + container.getChildren()); + } + + // add it + list.add(container); + } catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + @Override + public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the node ref + CMISNodeInfo info = getOrCreateFolderInfo(folderId, "Folder"); + + // the root folder has no parent + if (info.isRootFolder()) + { + throw new CmisInvalidArgumentException("Root folder has no parent!"); + } + + // get the parent + List parentInfos = info.getParents(); + if (parentInfos.isEmpty()) + { + throw new CmisRuntimeException("Folder has no parent and is not the root folder?!"); + } + + CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); + + ObjectData result = connector.createCMISObject(parentInfo, filter, false, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, parentInfo.getObjectId(), IncludeRelationships.NONE); + } + + return result; + } + + @Override + public List getObjectParents(String repositoryId, String objectId, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includeRelativePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships are not filed + if (info.isRelationship()) + { + throw new CmisConstraintException("Relationships are not fileable!"); + } + + if (info.isFolder() && !info.isRootFolder()) + { + List parentInfos = info.getParents(); + if (!parentInfos.isEmpty()) + { + CMISNodeInfo parentInfo = addNodeInfo(parentInfos.get(0)); + + ObjectData object = connector.createCMISObject(parentInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId(), includeRelationships); + } + + ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); + objectParent.setObject(object); + + // include relative path segment + if (includeRelativePathSegment) + { + objectParent.setRelativePathSegment(info.getName()); + } + + result.add(objectParent); + } + } else if (info.isCurrentVersion() || info.isPWC()) + { + List parentInfos = info.getParents(); + for (CMISNodeInfo parentInfo : parentInfos) + { + addNodeInfo(parentInfo); + + ObjectData object = connector.createCMISObject(parentInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId(), includeRelationships); + } + + ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); + objectParent.setObject(object); + + // include relative path segment + if (includeRelativePathSegment) + { + objectParent.setRelativePathSegment(info.getName()); + } + + result.add(objectParent); + } + } + + return result; + } + + @Override + public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + // prepare query + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + QueryParameterDefinition usernameDef = new QueryParameterDefImpl(PARAM_USERNAME, connector + .getDictionaryService().getDataType(DataTypeDefinition.TEXT), true, + AuthenticationUtil.getFullyAuthenticatedUser()); + params.addQueryParameterDefinition(usernameDef); + + if (folderId == null) + { + params.setQuery(LUCENE_QUERY_CHECKEDOUT); + params.addStore(connector.getRootStoreRef()); + } else + { + CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); + + params.setQuery(LUCENE_QUERY_CHECKEDOUT_IN_FOLDER); + params.addStore(folderInfo.getNodeRef().getStoreRef()); + QueryParameterDefinition parentDef = new QueryParameterDefImpl(PARAM_PARENT, connector + .getDictionaryService().getDataType(DataTypeDefinition.NODE_REF), true, folderInfo.getNodeRef() + .toString()); + params.addQueryParameterDefinition(parentDef); + } + + // set up order + if (orderBy != null) + { + String[] parts = orderBy.split(","); + for (int i = 0; i < parts.length; i++) + { + String[] sort = parts[i].split(" +"); + + if (sort.length < 1) + { + continue; + } + + PropertyDefinitionWrapper propDef = connector.getOpenCMISDictionaryService().findPropertyByQueryName( + sort[0]); + if (propDef != null) + { + if (propDef.getPropertyDefinition().isOrderable()) + { + QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); + if (sortProp != null) + { + boolean sortAsc = (sort.length == 1) || sort[1].equalsIgnoreCase("asc"); + params.addSort(propDef.getPropertyLuceneBuilder().getLuceneFieldName(), sortAsc); + } else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - mapping not found"); + } + } + } else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - not orderable"); + } + } + } else + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignore sort property '" + sort[0] + " - query name not found"); + } + } + } + } + + // execute query + ResultSet resultSet = null; + List nodeRefs; + try + { + resultSet = connector.getSearchService().query(params); + nodeRefs = resultSet.getNodeRefs(); + } finally + { + if (resultSet != null) + { + resultSet.close(); + } + } + + // collect results + ObjectListImpl result = new ObjectListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + int skipCounter = skip; + if (max > 0) + { + for (NodeRef nodeRef : nodeRefs) + { + if (skipCounter > 0) + { + skipCounter--; + continue; + } + + if (list.size() == max) + { + break; + } + + try + { + // create a CMIS object + CMISNodeInfo ni = createNodeInfo(nodeRef); + ObjectData object = connector.createCMISObject(ni, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, ni.getObjectId(), includeRelationships); + } + + // add it + list.add(object); + } catch (InvalidNodeRefException e) + { + // ignore invalid objects + } + } + } + + // has more ? + result.setHasMoreItems(nodeRefs.size() - skip > list.size()); + + return result; + } + + // --- object service --- + + @Override + public String create(String repositoryId, Properties properties, String folderId, ContentStream contentStream, + VersioningState versioningState, List policies, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // check properties + if (properties == null || properties.getProperties() == null) + { + throw new CmisInvalidArgumentException("Properties must be set!"); + } + + // get the type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + + // find the type + TypeDefinitionWrapper type = connector.getOpenCMISDictionaryService().findType(objectTypeId); + if (type == null) + { + throw new CmisInvalidArgumentException("Type '" + objectTypeId + "' is unknown!"); + } + + // create object + String newId = null; + switch (type.getBaseTypeId()) + { + case CMIS_DOCUMENT: + newId = createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, null, + null, extension); + break; + case CMIS_FOLDER: + newId = createFolder(repositoryId, properties, folderId, policies, null, null, extension); + break; + case CMIS_POLICY: + newId = createPolicy(repositoryId, properties, folderId, policies, null, null, extension); + break; + } + + // check new object id + if (newId == null) + { + throw new CmisRuntimeException("Creation failed!"); + } + + if (context.isObjectInfoRequired()) + { + try + { + getObjectInfo(repositoryId, newId, "*", IncludeRelationships.NONE); + } catch (InvalidNodeRefException e) + { + throw new CmisRuntimeException("Creation failed! New object not found!"); + } + } + + // return the new object id + return newId; + } + + @Override + public String createFolder(String repositoryId, final Properties properties, String folderId, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Folder"); + + // get name and type + final String name = connector.getNameProperty(properties, null); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_FOLDER); + + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef nodeRef = connector.getFileFolderService() + .create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); + + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, + PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + return nodeRef; + } catch (FileExistsException fee) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return newNodeRef.getId(); + } + + @Override + public String createDocument(String repositoryId, final Properties properties, String folderId, + final ContentStream contentStream, final VersioningState versioningState, final List policies, + final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); + + // get name and type + final String name = connector.getNameProperty(properties, null); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_DOCUMENT); + + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + DocumentTypeDefinition docType = (DocumentTypeDefinition) type.getTypeDefinition(false); + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) && (contentStream != null)) + { + throw new CmisConstraintException("This document type does not support content!"); + } + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) && (contentStream == null)) + { + throw new CmisConstraintException("This document type does requires content!"); + } + + if (docType.isVersionable() && (versioningState == VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is versionable!"); + } + + if (!docType.isVersionable() && (versioningState != VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is not versionable!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef nodeRef = connector.getFileFolderService() + .create(parentInfo.getNodeRef(), name, type.getAlfrescoClass()).getNodeRef(); + + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, + PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + connector.applyVersioningState(nodeRef, versioningState); + + return nodeRef; + } catch (FileExistsException fee) + { + removeTempFile(tempFile); + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + + return connector.createObjectId(newNodeRef); + } + + @Override + public String createDocumentFromSource(String repositoryId, String sourceId, final Properties properties, + String folderId, final VersioningState versioningState, final List policies, final Acl addAces, + final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final CMISNodeInfo parentInfo = getOrCreateFolderInfo(folderId, "Parent folder"); + + // get source + CMISNodeInfo info = getOrCreateNodeInfo(sourceId, "Source"); + + // check source + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + final NodeRef sourceNodeRef = info.getNodeRef(); + if (!info.isDocument()) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + // get name and type + final String name = connector.getNameProperty(properties, info.getName()); + + final TypeDefinitionWrapper type = info.getType(); + connector.checkChildObjectType(parentInfo, type.getTypeId()); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef newDocumentNodeRef = connector.getFileFolderService() + .copy(sourceNodeRef, parentInfo.getNodeRef(), name).getNodeRef(); + + connector.setProperties(newDocumentNodeRef, type, properties, new String[] { + PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(newDocumentNodeRef, type, policies); + connector.applyACL(newDocumentNodeRef, type, addAces, removeAces); + connector.applyVersioningState(newDocumentNodeRef, versioningState); + + return newDocumentNodeRef; + } catch (FileExistsException fee) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.createObjectId(newNodeRef); + } + + @Override + public String createPolicy(String repositoryId, Properties properties, String folderId, List policies, + Acl addAces, Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder + getOrCreateFolderInfo(folderId, "Parent Folder"); + + String objectTypeId = connector.getObjectTypeIdProperty(properties); + connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY); + + // we should never get here - policies are not creatable! + throw new CmisRuntimeException("Polcies cannot be created!"); + } + + @Override + public String createRelationship(String repositoryId, Properties properties, List policies, Acl addAces, + Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP); + + // get source object + String sourceId = connector.getSourceIdProperty(properties); + CMISNodeInfo sourceInfo = getOrCreateNodeInfo(sourceId, "Source"); + + if (!sourceInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !sourceInfo.isVariant(CMISObjectVariant.FOLDER)) + { + throw new CmisInvalidArgumentException("Source is not the latest version of a document or a folder object!"); + } + + final NodeRef sourceNodeRef = sourceInfo.getNodeRef(); + + // get target object + String targetId = connector.getTargetIdProperty(properties); + CMISNodeInfo targetInfo = getOrCreateNodeInfo(targetId, "Target"); + + if (!targetInfo.isVariant(CMISObjectVariant.CURRENT_VERSION) && !targetInfo.isVariant(CMISObjectVariant.FOLDER)) + { + throw new CmisInvalidArgumentException( + "Target is not the latest version of a document or a folder object!!"); + } + + final NodeRef targetNodeRef = targetInfo.getNodeRef(); + + // check policies and ACLs + if ((policies != null) && (!policies.isEmpty())) + { + throw new CmisConstraintException("Relationships are not policy controllable!"); + } + + if ((addAces != null) && (addAces.getAces() != null) && (!addAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + if ((removeAces != null) && (removeAces.getAces() != null) && (!removeAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + // create relationship + endReadOnlyTransaction(); + AssociationRef assocRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public AssociationRef execute() throws Exception + { + try + { + return connector.getNodeService().createAssociation(sourceNodeRef, targetNodeRef, + type.getAlfrescoClass()); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); + } + + @Override + public void setContentStream(String repositoryId, Holder objectId, Boolean overwriteFlag, + Holder changeToken, final ContentStream contentStream, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisStreamNotSupportedException("Content can only be set ondocuments!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) + { + throw new CmisStreamNotSupportedException("Document type doesn't allow content!"); + } + + boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null; + if (existed && !overwriteFlag) + { + throw new CmisContentAlreadyExistsException("Content already exists!"); + } + + if ((contentStream == null) || (contentStream.getStream() == null)) + { + throw new CmisInvalidArgumentException("No content!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + + return null; + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void deleteContentStream(String repositoryId, Holder objectId, Holder changeToken, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION) && !info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisStreamNotSupportedException("Content can only be deleted from ondocuments!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + if (((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) + { + throw new CmisInvalidArgumentException("Document type requires content!"); + } + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void moveObject(String repositoryId, Holder objectId, String targetFolderId, String sourceFolderId, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get object and source and target parent + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final CMISNodeInfo sourceInfo = getOrCreateFolderInfo(sourceFolderId, "Source Folder"); + final CMISNodeInfo targetInfo = getOrCreateFolderInfo(targetFolderId, "Target Folder"); + + connector.checkChildObjectType(targetInfo, info.getType().getTypeId()); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef); + // if this is a primary child node, move it + if (primaryParentRef.getParentRef().equals(sourceInfo.getNodeRef())) + { + connector.getNodeService().moveNode(nodeRef, targetInfo.getNodeRef(), + primaryParentRef.getTypeQName(), primaryParentRef.getQName()); + } else + { + // otherwise, reparent it + for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs(nodeRef, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) + { + if (parent.getParentRef().equals(sourceInfo.getNodeRef())) + { + connector.getNodeService().removeChildAssociation(parent); + connector.getNodeService().addChild(targetInfo.getNodeRef(), nodeRef, + ContentModel.ASSOC_CONTAINS, parent.getQName()); + return null; + } + } + throw new CMISInvalidArgumentException( + "Document is not a child of the source folder that was specified!"); + } + + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void updateProperties(String repositoryId, Holder objectId, Holder changeToken, + final Properties properties, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + final CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Relationship properties cannot be updated!"); + } else + { + if (info.isVariant(CMISObjectVariant.VERSION)) + { + throw new CmisInvalidArgumentException("Document is not the latest version!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + // run transaction + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.setProperties(nodeRef, info.getType(), properties, new String[0]); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + objectId.setValue(connector.createObjectId(nodeRef)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, objectId.getValue(), "*", IncludeRelationships.NONE); + } + } + } + + @Override + public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) + { + deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension); + } + + @Override + public void deleteObjectOrCancelCheckOut(String repositoryId, final String objectId, final Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + final CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // run transaction + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Boolean execute() throws Exception + { + try + { + // handle relationships + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + AssociationRef assocRef = info.getAssociationRef(); + connector.getNodeService().removeAssociation(assocRef.getSourceRef(), + assocRef.getTargetRef(), assocRef.getTypeQName()); + return true; + } + + NodeRef nodeRef = info.getNodeRef(); + + // handle PWC + if (info.isVariant(CMISObjectVariant.PWC)) + { + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + return true; + } + + // handle folders + if (info.isFolder()) + { + if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0) + { + throw new CmisConstraintException( + "Could not delete folder with at least one child!"); + } + + connector.getNodeService().deleteNode(nodeRef); + return true; + } + + // handle versions + if (allVersions) + { + NodeRef workingCopy = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (workingCopy != null) + { + connector.getCheckOutCheckInService().cancelCheckout(workingCopy); + } + } else if (info.isVariant(CMISObjectVariant.VERSION)) + { + Version version = ((CMISNodeInfoImpl) info).getVersion(); + connector.getVersionService().deleteVersion(nodeRef, version); + return true; + } + + if (info.isVariant(CMISObjectVariant.VERSION)) + { + nodeRef = info.getCurrentNodeNodeRef(); + } + + // remove not primary parent associations + List childAssociations = connector.getNodeService().getParentAssocs( + nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + if (allVersions) + { + connector.getNodeService().deleteNode(nodeRef); + } else + { + CMISNodeInfoImpl infoImpl = ((CMISNodeInfoImpl) info); + Version version = infoImpl.getVersion(); + + if (infoImpl.getVersionHistory().getPredecessor(version) == null) + { + connector.getNodeService().deleteNode(nodeRef); + } else + { + connector.getVersionService().deleteVersion(nodeRef, version); + } + } + return true; + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + endReadOnlyTransaction(); + } + + @Override + public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions, + UnfileObject unfileObjects, final Boolean continueOnFailure, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + if (unfileObjects == UnfileObject.UNFILE) + { + throw new CmisInvalidArgumentException("Unfiling not supported!"); + } + + final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + final FailedToDeleteDataImpl result = new FailedToDeleteDataImpl(); + + // run transaction + endReadOnlyTransaction(); + result.setIds(connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback>() + { + public List execute() throws Exception + { + return deleteBranch(folderNodeRef, continueOnFailure); + }; + }, false, true)); + beginReadOnlyTransaction(); + + return result; + } + + private List deleteBranch(NodeRef nodeRef, boolean continueOnFailure) + { + List result = new ArrayList(); + + try + { + // remove children + List childrenList = connector.getNodeService().getChildAssocs(nodeRef); + if (childrenList != null) + { + for (ChildAssociationRef child : childrenList) + { + List ftod = deleteBranch(child.getChildRef(), continueOnFailure); + if (!ftod.isEmpty()) + { + result.addAll(ftod); + if (!continueOnFailure) + { + return result; + } + } + } + } + + // remove not primary parent associations + List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + connector.getNodeService().deleteNode(nodeRef); + } catch (Exception e) + { + result.add(nodeRef.getId()); + } + + return result; + } + + @Override + public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // create a CMIS object + ObjectData object = connector.createCMISObject(info, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } + + @Override + public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // start at the root node + NodeRef rootNodeRef = connector.getRootNodeRef(); + + if (path.equals("/")) + { + return connector.createCMISObject(createNodeInfo(rootNodeRef), filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + } else + { + try + { + // resolve path and get the node ref + FileInfo fileInfo = connector.getFileFolderService().resolveNamePath(rootNodeRef, + Arrays.asList(path.substring(1).split("/"))); + + CMISNodeInfo info = createNodeInfo(fileInfo.getNodeRef()); + + ObjectData object = connector.createCMISObject(info, fileInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } catch (FileNotFoundException e) + { + throw new CmisObjectNotFoundException("Object not found: " + path); + } + } + } + + @Override + public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); + } + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return connector.getAssocProperties(info, filter); + } else + { + return connector.getNodeProperties(info, filter); + } + } + + @Override + public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + return connector.getAllowableActions(info); + } + + @Override + public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset, + BigInteger length, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships cannot have content + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Object is a relationship and cannot have content!"); + } + + // now get it + return connector.getContentStream(info, streamId, offset, length); + } + + @Override + public List getRenditions(String repositoryId, String objectId, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return Collections.emptyList(); + } else + { + return connector.getRenditions(info.getNodeRef(), renditionFilter, maxItems, skipCount); + } + } + + // --- versioning service --- + + @Override + public void checkOut(String repositoryId, final Holder objectId, ExtensionsData extension, + final Holder contentCopied) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + // check for current version + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) + { + throw new CmisInvalidArgumentException("Only documents can be checked out!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + + if (!((DocumentTypeDefinition) info.getType().getTypeDefinition(false)).isVersionable()) + { + throw new CmisConstraintException("Document is not versionable!"); + } + + // check out + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().checkout(nodeRef); + CMISNodeInfo pwcNodeInfo = createNodeInfo(pwcNodeRef); + objectId.setValue(pwcNodeInfo.getObjectId()); + + if (contentCopied != null) + { + contentCopied.setValue(connector.getFileFolderService().getReader(pwcNodeRef) != null); + } + return null; + } catch (CheckOutCheckInServiceException e) + { + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // only accept a PWC + if (!info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + + // cancel check out + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + return null; + } catch (CheckOutCheckInServiceException e) + { + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void checkIn(String repositoryId, final Holder objectId, final Boolean major, + final Properties properties, final ContentStream contentStream, final String checkinComment, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId.getValue(), "Object"); + + // only accept a PWC + if (!info.isVariant(CMISObjectVariant.PWC)) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = info.getNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + // check in + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + // update PWC + connector.setProperties(nodeRef, type, properties, + new String[] { PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + // check aspect + if (connector.getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_INITIAL_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, false); + connector.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + + // create version properties + Map versionProperties = new HashMap(5); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR + : VersionType.MINOR); + if (checkinComment != null) + { + versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment); + } + + // check in + NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, + versionProperties); + + objectId.setValue(connector.createObjectId(newNodeRef)); + + return null; + } catch (FileExistsException fee) + { + removeTempFile(tempFile); + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (CheckOutCheckInServiceException e) + { + removeTempFile(tempFile); + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + } + + @Override + public List getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter, + Boolean includeAllowableActions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (versionSeriesId == null && objectId != null) + { + // it's a browser binding call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + if (versionSeriesId == null) + { + throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!"); + } + + List result = new ArrayList(); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + + if (!info.isVariant(CMISObjectVariant.CURRENT_VERSION)) + { + // the version series id is the id of current version, which is a + // document + throw new CmisInvalidArgumentException("Version Series does not exist!"); + } + + // get current version and it's history + NodeRef nodeRef = info.getNodeRef(); + VersionHistory versionHistory = ((CMISNodeInfoImpl) info).getVersionHistory(); + + if (versionHistory == null) + { + // add current version + result.add(connector.createCMISObject(info, filter, includeAllowableActions, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), IncludeRelationships.NONE); + } + } else + { + if (info.hasPWC()) + { + CMISNodeInfo pwcInfo = createNodeInfo(connector.getCheckOutCheckInService().getWorkingCopy(nodeRef)); + + result.add(connector.createCMISObject(pwcInfo, filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, pwcInfo.getObjectId(), IncludeRelationships.NONE); + } + } + + // convert the version history + for (Version version : versionHistory.getAllVersions()) + { + CMISNodeInfo versionInfo = createNodeInfo(version.getFrozenStateNodeRef()); + + result.add(connector.createCMISObject(versionInfo, filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, versionInfo.getObjectId(), IncludeRelationships.NONE); + } + } + } + + return result; + } + + @Override + public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); + + ObjectData object = connector.createCMISObject(versionInfo, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, info.getObjectId(), includeRelationships); + } + + return object; + } + + @Override + public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(versionSeriesId, "Version Series"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return connector.getAssocProperties(info, filter); + } else + { + CMISNodeInfo versionInfo = createNodeInfo(((CMISNodeInfoImpl) info).getLatestVersionNodeRef(major)); + addNodeInfo(versionInfo); + return connector.getNodeProperties(versionInfo, filter); + } + } + + // --- multifiling service --- + + @Override + public void addObjectToFolder(String repositoryId, String objectId, String folderId, Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + // get node ref + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (!info.isDocument()) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + // get the folder node ref + final CMISNodeInfo folderInfo = getOrCreateFolderInfo(folderId, "Folder"); + + connector.checkChildObjectType(folderInfo, info.getType().getTypeId()); + + final QName name = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName((String) connector.getNodeService().getProperty(nodeRef, + ContentModel.PROP_NAME))); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().addChild(folderInfo.getNodeRef(), nodeRef, + ContentModel.ASSOC_CONTAINS, name); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get node ref + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (!info.isDocument()) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + final NodeRef nodeRef = info.getNodeRef(); + + // get the folder node ref + final NodeRef folderNodeRef = getOrCreateFolderInfo(folderId, "Folder").getNodeRef(); + + // check primary parent + if (connector.getNodeService().getPrimaryParent(nodeRef).getParentRef().equals(folderNodeRef)) + { + throw new CmisConstraintException( + "Unfiling from primary parent folder is not supported! Use deleteObject() instead."); + } + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().removeChild(folderNodeRef, nodeRef); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + // --- discovery service --- + + @Override + public ObjectList getContentChanges(String repositoryId, Holder changeLogToken, Boolean includeProperties, + String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return connector.getContentChanges(changeLogToken, maxItems); + } + + @Override + public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (searchAllVersions.booleanValue()) + { + throw new CmisInvalidArgumentException("Search all version is not supported!"); + } + + return connector.query(statement, includeAllowableActions, includeRelationships, renditionFilter, maxItems, + skipCount); + } + + // --- relationship service --- + + @Override + public ObjectList getObjectRelationships(String repositoryId, String objectId, Boolean includeSubRelationshipTypes, + RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisInvalidArgumentException("Object is a relationship!"); + } + + if (info.isVariant(CMISObjectVariant.VERSION)) + { + throw new CmisInvalidArgumentException("Object is a document version!"); + } + + // check if the relationship base type is requested + if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId)) + { + boolean isrt = (includeSubRelationshipTypes == null ? false : includeSubRelationshipTypes.booleanValue()); + if (isrt) + { + // all relationships are a direct subtype of the base type in + // Alfresco -> remove filter + typeId = null; + } else + { + // there are no relationships of the base type in Alfresco -> + // return empty list + ObjectListImpl result = new ObjectListImpl(); + result.setHasMoreItems(false); + result.setNumItems(BigInteger.ZERO); + result.setObjects(new ArrayList()); + return result; + } + } + + return connector.getObjectRelationships(info.getNodeRef(), relationshipDirection, typeId, filter, + includeAllowableActions, maxItems, skipCount); + } + + // --- policy service --- + + @Override + public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + TypeDefinitionWrapper type = info.getType(); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + connector.applyPolicies(info.getNodeRef(), type, Collections.singletonList(policyId)); + } + + @Override + public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + TypeDefinitionWrapper type = info.getType(); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + throw new CmisConstraintException("Object is not policy controllable!"); + } + + @Override + public List getAppliedPolicies(String repositoryId, String objectId, String filter, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + getOrCreateNodeInfo(objectId, "Object"); + + // policies are not supported -> return empty list + return Collections.emptyList(); + } + + // --- ACL service --- + + @Override + public Acl applyAcl(String repositoryId, String objectId, final Acl addAces, final Acl removeAces, + AclPropagation aclPropagation, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.applyACL(nodeRef, type, addAces, removeAces); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.getACL(nodeRef, false); + } + + @Override + public Acl applyAcl(String repositoryId, String objectId, final Acl aces, AclPropagation aclPropagation) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = info.getCurrentNodeNodeRef(); + final TypeDefinitionWrapper type = info.getType(); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.applyACL(nodeRef, type, aces); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.getACL(nodeRef, false); + } + + @SuppressWarnings("unchecked") + @Override + public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + CMISNodeInfo info = getOrCreateNodeInfo(objectId, "Object"); + + // relationships don't have ACLs + if (info.isVariant(CMISObjectVariant.ASSOC)) + { + return new AccessControlListImpl(Collections.EMPTY_LIST); + } + + // get the ACL + return connector.getACL(info.getCurrentNodeNodeRef(), onlyBasicPermissions); + } + + // -------------------------------------------------------- + + /** + * Collects the {@link ObjectInfo} about an object. + * + * @param context the context in which the service must operate + */ + @Override + public ObjectInfo getObjectInfo(String repositoryId, String objectId) + { + return getObjectInfo(repositoryId, objectId, null, IncludeRelationships.BOTH); + } + + public ObjectInfo getObjectInfo(String repositoryId, String objectId, IncludeRelationships includeRelationships) + { + return getObjectInfo(repositoryId, objectId, null, includeRelationships); + } + + public ObjectInfo getObjectInfo(String repositoryId, String objectId, String filter, + IncludeRelationships includeRelationships) + { + ObjectInfo info = objectInfoMap.get(objectId); + if (info == null) + { + CMISNodeInfo nodeInfo = getOrCreateNodeInfo(objectId); + + if (nodeInfo.getObjectVariant() == CMISObjectVariant.INVALID_ID + || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_EXISTING + || nodeInfo.getObjectVariant() == CMISObjectVariant.NOT_A_CMIS_OBJECT + || nodeInfo.getObjectVariant() == CMISObjectVariant.PERMISSION_DENIED) + { + info = null; + } else + { + // object info has not been found -> create one + try + { + if (filter == null) + { + filter = MIN_FILTER; + } else if (!filter.equals("*")) + { + filter = filter + "," + MIN_FILTER; + } + + // get the object and its info + ObjectData object = connector.createCMISObject(nodeInfo, filter, false, includeRelationships, null, + false, false); + + info = getObjectInfoIntern(repositoryId, object); + + // add object info + objectInfoMap.put(objectId, info); + } catch (Exception e) + { + e.printStackTrace(); + info = null; + } + } + } + + return info; + } + + protected String getGuid(String nodeId) + { + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } + } + + /** + * Collects the {@link ObjectInfo} about an object. + * + * (Provided by OpenCMIS, but optimized for Alfresco.) + */ + @Override + protected ObjectInfo getObjectInfoIntern(String repositoryId, ObjectData object) + { + // if the object has no properties, stop here + if (object.getProperties() == null || object.getProperties().getProperties() == null) + { + throw new CmisRuntimeException("No properties!"); + } + + CMISNodeInfo ni = getOrCreateNodeInfo(object.getId()); + + ObjectInfoImpl info = new ObjectInfoImpl(); + + // general properties + info.setObject(object); + info.setId(object.getId()); + info.setName(ni.getName()); + info.setCreatedBy(getStringProperty(object, PropertyIds.CREATED_BY)); + info.setCreationDate(getDateTimeProperty(object, PropertyIds.CREATION_DATE)); + info.setLastModificationDate(getDateTimeProperty(object, PropertyIds.LAST_MODIFICATION_DATE)); + info.setTypeId(getIdProperty(object, PropertyIds.OBJECT_TYPE_ID)); + info.setBaseType(object.getBaseTypeId()); + + if (ni.isRelationship()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(null); + info.setIsCurrentVersion(true); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + // parent + info.setHasParent(false); + + // policies and relationships + info.setSupportsRelationships(false); + info.setSupportsPolicies(false); + + // renditions + info.setRenditionInfos(null); + + // relationships + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + + // global settings + info.setHasAcl(false); + info.setSupportsDescendants(false); + info.setSupportsFolderTree(false); + } else if (ni.isFolder()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(null); + info.setIsCurrentVersion(true); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + // parent + info.setHasParent(!ni.isRootFolder()); + + // policies and relationships + info.setSupportsRelationships(true); + info.setSupportsPolicies(true); + + // renditions + info.setRenditionInfos(null); + + // relationships + setRelaionshipsToObjectInfo(object, info); + + // global settings + info.setHasAcl(true); + info.setSupportsDescendants(true); + info.setSupportsFolderTree(true); + } else if (ni.isDocument()) + { + // versioning + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(getGuid(ni.getCurrentNodeId())); + + if (ni.isPWC()) + { + info.setIsCurrentVersion(false); + info.setWorkingCopyId(ni.getObjectId()); + info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); + } else + { + info.setIsCurrentVersion(ni.isCurrentVersion()); + + if (ni.hasPWC()) + { + info.setWorkingCopyId(getGuid(ni.getCurrentNodeId()) + CMISConnector.ID_SEPERATOR + + CMISConnector.PWC_VERSION_LABEL); + info.setWorkingCopyOriginalId(ni.getCurrentObjectId()); + } else + { + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + } + } + + // content + String fileName = getStringProperty(object, PropertyIds.CONTENT_STREAM_FILE_NAME); + String mimeType = getStringProperty(object, PropertyIds.CONTENT_STREAM_MIME_TYPE); + String streamId = getIdProperty(object, PropertyIds.CONTENT_STREAM_ID); + BigInteger length = getIntegerProperty(object, PropertyIds.CONTENT_STREAM_LENGTH); + boolean hasContent = fileName != null || mimeType != null || streamId != null || length != null; + if (hasContent) + { + info.setHasContent(hasContent); + info.setContentType(mimeType); + info.setFileName(fileName); + } else + { + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + } + + // parent + info.setHasParent(ni.isCurrentVersion() || ni.isPWC()); + + // policies and relationships + info.setSupportsRelationships(true); + info.setSupportsPolicies(true); + + // renditions + info.setRenditionInfos(null); + List renditions = object.getRenditions(); + if (renditions != null && renditions.size() > 0) + { + List renditionInfos = new ArrayList(); + for (RenditionData rendition : renditions) + { + RenditionInfoImpl renditionInfo = new RenditionInfoImpl(); + renditionInfo.setId(rendition.getStreamId()); + renditionInfo.setKind(rendition.getKind()); + renditionInfo.setContentType(rendition.getMimeType()); + renditionInfo.setTitle(rendition.getTitle()); + renditionInfo.setLength(rendition.getBigLength()); + renditionInfos.add(renditionInfo); + } + info.setRenditionInfos(renditionInfos); + } + + // relationships + setRelaionshipsToObjectInfo(object, info); + + // global settings + info.setHasAcl(true); + info.setSupportsDescendants(true); + info.setSupportsFolderTree(true); + } + + return info; + } + + private void setRelaionshipsToObjectInfo(ObjectData object, ObjectInfoImpl info) + { + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + + List relationships = object.getRelationships(); + if (relationships != null && relationships.size() > 0) + { + List sourceIds = new ArrayList(); + List targetIds = new ArrayList(); + for (ObjectData relationship : relationships) + { + String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID); + String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID); + if (object.getId().equals(sourceId)) + { + sourceIds.add(relationship.getId()); + } + if (object.getId().equals(targetId)) + { + targetIds.add(relationship.getId()); + } + } + if (sourceIds.size() > 0) + { + info.setRelationshipSourceIds(sourceIds); + } + if (targetIds.size() > 0) + { + info.setRelationshipTargetIds(targetIds); + } + } + } + + // -------------------------------------------------------- + + protected void checkRepositoryId(String repositoryId) + { + if (!connector.getRepositoryId().equals(repositoryId)) + { + throw new CmisObjectNotFoundException("Unknown repository '" + repositoryId + "'!"); + } + } + + private Charset getEncoding(File tempFile, String mimeType) + { + Charset encoding = null; + + try + { + InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); + ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); + encoding = charsetFinder.getCharset(tfis, mimeType); + tfis.close(); + } catch (Exception e) + { + throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); + } + + return encoding; + } + + private File copyToTempFile(ContentStream contentStream) + { + File tempFile = null; + + if (contentStream == null) + { + return tempFile; + } + + int bufferSize = 40 * 1014; + long count = 0; + + try + { + tempFile = TempFileProvider.createTempFile("cmis", "content"); + if (contentStream.getStream() != null) + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); + InputStream in = new BufferedInputStream(contentStream.getStream(), bufferSize); + + byte[] buffer = new byte[bufferSize]; + int i; + while ((i = in.read(buffer)) > -1) + { + out.write(buffer, 0, i); + count += i; + } + + in.close(); + out.close(); + } + } catch (Exception e) + { + removeTempFile(tempFile); + throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); + } + + if (contentStream.getLength() > -1 && contentStream.getLength() != count) + { + removeTempFile(tempFile); + throw new CmisStorageException("Expected " + contentStream.getLength() + " bytes but retrieved " + count + + "bytes!"); + } + + return tempFile; + } + + private void removeTempFile(File tempFile) + { + if (tempFile == null) + { + return; + } + + try + { + tempFile.delete(); + } catch (Exception e) + { + // ignore - file will be removed by TempFileProvider + } + } +} diff --git a/source/java/org/alfresco/opencmis/BaseCMISTest.java b/source/java/org/alfresco/opencmis/BaseCMISTest.java index a0146dbd99..95461bcfdc 100644 --- a/source/java/org/alfresco/opencmis/BaseCMISTest.java +++ b/source/java/org/alfresco/opencmis/BaseCMISTest.java @@ -61,7 +61,7 @@ import org.springframework.context.ApplicationContext; */ public abstract class BaseCMISTest extends TestCase { - private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + protected static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); protected CMISMapping cmisMapping; @@ -111,6 +111,8 @@ public abstract class BaseCMISTest extends TestCase protected FullTextSearchIndexer luceneFTS; + protected StoreRef storeRef; + public void setUp() throws Exception { serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); @@ -152,7 +154,7 @@ public abstract class BaseCMISTest extends TestCase this.authenticationComponent.setSystemUserAsCurrentUser(); String storeName = "CMISTest-" + getStoreName() + "-" + (new Date().getTime()); - StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, storeName); + this.storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, storeName); rootNodeRef = nodeService.getRootNode(storeRef); if(authenticationDAO.userExists("cmis")) diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index c206aa4728..1cdbc2b48f 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -288,9 +288,17 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen private List repositoryPermissions; private Map permissionMappings; + private ObjectFilter objectFilter; + + public void setObjectFilter(ObjectFilter objectFilter) + { + this.objectFilter = objectFilter; + } + // -------------------------------------------------------------- // Configuration // -------------------------------------------------------------- + /** * Sets the root store. * @@ -707,12 +715,20 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen // -------------------------------------------------------------- // Alfresco methods // -------------------------------------------------------------- - + public SiteInfo getSite(NodeRef nodeRef) { return siteService.getSite(nodeRef); } + /** + * Should the node be filtered? + */ + public boolean filter(NodeRef nodeRef) + { + return objectFilter.filter(nodeRef); + } + public boolean disableBehaviour(QName className, NodeRef nodeRef) { boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className); @@ -871,17 +887,19 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { if(getFileFolderService().getFileInfo(currentVersionNodeRef).isFolder()) { - return currentVersionNodeRef.toString(); + // TODO code convergence - refer to public API and CLOUD-1267 - this needs to be resolved !! + return currentVersionNodeRef.getId(); } Serializable versionLabel = getNodeService() .getProperty(currentVersionNodeRef, ContentModel.PROP_VERSION_LABEL); if (versionLabel == null) { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; } - return currentVersionNodeRef.toString() + CMISConnector.ID_SEPERATOR + versionLabel; + // TODO code convergence - refer to public API and CLOUD-1267 - this needs to be resolved !! + return currentVersionNodeRef.getId() + CMISConnector.ID_SEPERATOR + versionLabel; } /** @@ -1049,11 +1067,11 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen } @SuppressWarnings("unchecked") - private ObjectData createCMISObjectImpl(CMISNodeInfo info, Properties nodeProps, String filter, + private ObjectData createCMISObjectImpl(final CMISNodeInfo info, Properties nodeProps, String filter, boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, boolean includeAcl) { - ObjectDataImpl result = new ObjectDataImpl(); + final ObjectDataImpl result = new ObjectDataImpl(); // set allowable actions if (includeAllowableActions) @@ -1103,7 +1121,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen // set ACL if (includeAcl) { - result.setAcl(getACL(info.getCurrentNodeNodeRef(), false)); + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + result.setAcl(getACL(info.getCurrentNodeNodeRef(), false)); + return null; + } + }); } // add aspects @@ -1196,7 +1222,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen CMISNodeInfo streamInfo = createNodeInfo(streamId); if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION)) { - throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId); + throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId + ", expected variant " + CMISObjectVariant.CURRENT_VERSION + ", got variant " + streamInfo.getObjectVariant()); } streamNodeRef = streamInfo.getNodeRef(); @@ -1549,7 +1575,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { if (value instanceof NodeRef) { - ((PropertyIdImpl) result).setValue(value.toString()); + ((PropertyIdImpl) result).setValue(((NodeRef)value).getId()); } else { @@ -2547,7 +2573,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen } catch (FileNotFoundException e) { - throw new CmisInvalidArgumentException("Object with id " + nodeRef.toString() + " not found!"); + throw new CmisInvalidArgumentException("Object with id " + nodeRef.getId() + " not found!"); } } else @@ -2890,7 +2916,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen ri.setVendorName("Alfresco"); ri.setProductName("Alfresco " + descriptorService.getServerDescriptor().getEdition()); ri.setProductVersion(currentDescriptor.getVersion()); - ri.setRootFolder(getRootNodeRef().toString()); + ri.setRootFolder(getRootNodeRef().getId()); ri.setCmisVersionSupported("1.0"); ri.setChangesIncomplete(true); diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index 19e158a13b..ee609d3328 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -131,7 +131,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo if (versionHistory == null) { objecVariant = CMISObjectVariant.CURRENT_VERSION; - objectId = nodeRef.toString() + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; + objectId = getGuid(nodeRef.toString()) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; currentObjectId = objectId; hasPWC = connector.getCheckOutCheckInService().isCheckedOut(nodeRef); @@ -141,8 +141,8 @@ public class CMISNodeInfoImpl implements CMISNodeInfo Version headVersion = versionHistory.getHeadVersion(); versionLabel = (String) connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - objectId = headVersion.getVersionedNodeRef().toString() + CMISConnector.ID_SEPERATOR + versionLabel; - currentObjectId = headVersion.getVersionedNodeRef().toString() + CMISConnector.ID_SEPERATOR + objectId = getGuid(headVersion.getVersionedNodeRef().toString()) + CMISConnector.ID_SEPERATOR + versionLabel; + currentObjectId = getGuid(headVersion.getVersionedNodeRef().toString()) + CMISConnector.ID_SEPERATOR + headVersion.getVersionLabel(); currentNodeId = headVersion.getVersionedNodeRef().toString(); @@ -161,8 +161,8 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; } - objectId = nodeRef.toString() + CMISConnector.ID_SEPERATOR + versionLabel; - currentObjectId = nodeRef.toString() + CMISConnector.ID_SEPERATOR + versionLabel; + objectId = getGuid(nodeRef.toString()) + CMISConnector.ID_SEPERATOR + versionLabel; + currentObjectId = getGuid(nodeRef.toString()) + CMISConnector.ID_SEPERATOR + versionLabel; currentNodeId = nodeRef.toString(); objecVariant = CMISObjectVariant.CURRENT_VERSION; hasPWC = connector.getCheckOutCheckInService().isCheckedOut(nodeRef); @@ -176,11 +176,25 @@ public class CMISNodeInfoImpl implements CMISNodeInfo protected void setUnversioned(NodeRef nodeRef) { objecVariant = CMISObjectVariant.CURRENT_VERSION; - objectId = nodeRef.toString() + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; + objectId = getGuid(nodeRef.toString()) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; currentObjectId = objectId; } - + + // TODO code convergence - refer to public API and CLOUD-1267 - this needs to be resolved !! + private String getGuid(String nodeId) + { + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } + } + protected void analyseObjectId() { currentNodeId = objectId; @@ -188,7 +202,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo versionLabel = null; nodeRef = null; hasPWC = false; - + if (objectId == null) { objecVariant = CMISObjectVariant.INVALID_ID; @@ -205,144 +219,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo versionLabel = objectId.substring(sepIndex + 1); } - if (NodeRef.isNodeRef(currentNodeId)) - { - // nodeRef is a "live" node, the version label identifies the specific version of the node - nodeRef = new NodeRef(currentNodeId); - - // check for existence - if (!connector.getNodeService().exists(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - // check PWC - if (connector.getCheckOutCheckInService().isWorkingCopy(nodeRef)) - { - NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); - objecVariant = CMISObjectVariant.PWC; - currentObjectId = connector.createObjectId(checkedOut); - currentNodeId = checkedOut.toString(); - versionLabel = CMISConnector.PWC_VERSION_LABEL; - hasPWC = true; - return; - } - - if (isFolder()) - { - // folders can't be versioned, so no need to check - objecVariant = CMISObjectVariant.FOLDER; - return; - } - - if (versionLabel == null) - { - if (isDocument()) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - - // Is it un-versioned, or currently versioned? - Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); - if (currentVersion != null) - { - versionLabel = currentVersion.getVersionLabel(); - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); - } - else - { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - } - - objectId = currentNodeId + CMISConnector.ID_SEPERATOR + versionLabel; - currentObjectId = objectId; - hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); - } else - { - objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; - } - return; - } - - // check if it has PWC label - if (versionLabel.equals(CMISConnector.PWC_VERSION_LABEL)) - { - NodeRef pwcNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); - if (pwcNodeRef == null) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - - objecVariant = CMISObjectVariant.PWC; - currentObjectId = connector.createObjectId(nodeRef); - currentNodeId = nodeRef.toString(); - hasPWC = true; - nodeRef = pwcNodeRef; - return; - } - - if(!connector.getVersionService().isVersioned(nodeRef)) - { - // the node isn't versioned - if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - - } else - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - else - { - // the node is versioned, determine whether the versionLabel refers to the head version or a - // specific non-head version - String headVersionLabel = (String)connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - currentObjectId = currentNodeId + CMISConnector.ID_SEPERATOR + headVersionLabel; - - if (versionLabel.equals(headVersionLabel)) - { - // the version label refers to the current head version - objecVariant = CMISObjectVariant.CURRENT_VERSION; - } - else - { - // the version label refers to a specific non-head version, find the nodeRef - // of the version node from the version history - versionHistory = getVersionHistory(); - if (versionHistory == null) - { - // unexpected null versionHistory, assume not versioned - if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - - } - else - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - else - { - try - { - version = versionHistory.getVersion(versionLabel); - nodeRef = version.getFrozenStateNodeRef(); - objecVariant = CMISObjectVariant.VERSION; - } - catch (VersionDoesNotExistException e) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - } - } - } - - // check if checked out - hasPWC = connector.getCheckOutCheckInService().isCheckedOut(nodeRef); - } else if (objectId.startsWith(CMISConnector.ASSOC_ID_PREFIX)) + if (objectId.startsWith(CMISConnector.ASSOC_ID_PREFIX)) { // check the association id Long assocId = null; @@ -364,11 +241,185 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { objecVariant = CMISObjectVariant.ASSOC; } - } else - { - objecVariant = CMISObjectVariant.INVALID_ID; } - } catch (AccessDeniedException e) + else + { + currentNodeId = connector.getRootStoreRef() + "/" + currentNodeId; + + if (NodeRef.isNodeRef(currentNodeId)) + { + // nodeRef is a "live" node, the version label identifies the specific version of the node + nodeRef = new NodeRef(currentNodeId); + + // check for existence + if (!connector.getNodeService().exists(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + + // check PWC + if (connector.getCheckOutCheckInService().isWorkingCopy(nodeRef)) + { + NodeRef checkedOut = connector.getCheckOutCheckInService().getCheckedOut(nodeRef); + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.PWC; + } + currentObjectId = connector.createObjectId(checkedOut); + currentNodeId = checkedOut.toString(); + versionLabel = CMISConnector.PWC_VERSION_LABEL; + hasPWC = true; + return; + } + + if (isFolder()) + { + // folders can't be versioned, so no need to check + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.FOLDER; + } + return; + } + + if (versionLabel == null) + { + if (isDocument()) + { + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } + objectId = getGuid(currentNodeId) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + currentObjectId = objectId; + hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); + } else + { + objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; + } + return; + } + + // check if it has PWC label + if (versionLabel.equals(CMISConnector.PWC_VERSION_LABEL)) + { + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (pwcNodeRef == null) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + else if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.PWC; + } + currentObjectId = connector.createObjectId(nodeRef); + currentNodeId = nodeRef.toString(); + hasPWC = true; + nodeRef = pwcNodeRef; + return; + } + + // check version + if(!connector.getVersionService().isVersioned(nodeRef)) + { + // the node isn't versioned + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } else + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + + // check if checked out + hasPWC = connector.getCheckOutCheckInService().isCheckedOut(getCurrentNodeNodeRef()); + + return; + } + + try + { + // the node is versioned, determine whether the versionLabel refers to the head version or a + // specific non-head version + String headVersionLabel = (String)connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + currentObjectId = currentNodeId + CMISConnector.ID_SEPERATOR + headVersionLabel; + + if (versionLabel.equals(headVersionLabel)) + { + // the version label refers to the current head version + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } + else + { + // the version label refers to a specific non-head version, find the nodeRef + // of the version node from the version history + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) + { + // unexpected null versionHistory, assume not versioned + if (versionLabel.equals(CMISConnector.UNVERSIONED_VERSION_LABEL)) + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + + } + else + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + } + else + { + try + { + version = versionHistory.getVersion(versionLabel); + nodeRef = version.getFrozenStateNodeRef(); + objecVariant = CMISObjectVariant.VERSION; + } + catch (VersionDoesNotExistException e) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + } + } + } + catch (VersionDoesNotExistException e) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + + // check if checked out + hasPWC = connector.getCheckOutCheckInService().isCheckedOut(getCurrentNodeNodeRef()); + } + else + { + objecVariant = CMISObjectVariant.INVALID_ID; + } + } + } + catch (AccessDeniedException e) { objecVariant = CMISObjectVariant.PERMISSION_DENIED; } @@ -393,11 +444,17 @@ public class CMISNodeInfoImpl implements CMISNodeInfo objecVariant = CMISObjectVariant.NOT_EXISTING; return; } - + + if (connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + if (isFolder()) { objecVariant = CMISObjectVariant.FOLDER; - objectId = nodeRef.toString(); + objectId = getGuid(nodeRef.toString()); currentObjectId = objectId; return; } @@ -406,7 +463,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; return; } - + // check PWC if (connector.getCheckOutCheckInService().isWorkingCopy(nodeRef)) { @@ -418,7 +475,9 @@ public class CMISNodeInfoImpl implements CMISNodeInfo } objecVariant = CMISObjectVariant.PWC; - objectId = checkedOut.toString() + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; + + objectId = getGuid(checkedOut.toString()) + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; + versionLabel = CMISConnector.PWC_VERSION_LABEL; currentObjectId = connector.createObjectId(checkedOut); currentNodeId = checkedOut.toString(); diff --git a/source/java/org/alfresco/opencmis/CMISRenditionMapping.java b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java index c37e3ae119..7fdd94c2af 100644 --- a/source/java/org/alfresco/opencmis/CMISRenditionMapping.java +++ b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java @@ -292,7 +292,7 @@ public class CMISRenditionMapping { RenditionDataImpl result = new RenditionDataImpl(); - result.setStreamId(rendNodeRef.toString()); + result.setStreamId(rendNodeRef.getId()); result.setMimeType(mimeType); result.setTitle(title); @@ -302,7 +302,7 @@ public class CMISRenditionMapping result.setBigWidth(width); result.setBigHeight(height); - result.setRenditionDocumentId(rendNodeRef.toString()); + result.setRenditionDocumentId(rendNodeRef.getId()); return result; } diff --git a/source/java/org/alfresco/opencmis/CMISTest.java b/source/java/org/alfresco/opencmis/CMISTest.java index ea2d02a35c..f88e7016cb 100644 --- a/source/java/org/alfresco/opencmis/CMISTest.java +++ b/source/java/org/alfresco/opencmis/CMISTest.java @@ -214,7 +214,7 @@ public class CMISTest ContentStreamImpl contentStream = new ContentStreamImpl(fileName, MimetypeMap.MIMETYPE_TEXT_PLAIN, "Simple text plain document"); // create simple text plain content - String objectId = cmisService.create(repositoryId, properties, repositoryHelper.getCompanyHome().toString(), contentStream, VersioningState.MAJOR, null, null); + String objectId = cmisService.create(repositoryId, properties, repositoryHelper.getCompanyHome().getId(), contentStream, VersioningState.MAJOR, null, null); Holder objectIdHolder = new Holder(objectId); diff --git a/source/java/org/alfresco/opencmis/ObjectFilter.java b/source/java/org/alfresco/opencmis/ObjectFilter.java new file mode 100644 index 0000000000..e7ba25d597 --- /dev/null +++ b/source/java/org/alfresco/opencmis/ObjectFilter.java @@ -0,0 +1,8 @@ +package org.alfresco.opencmis; + +import org.alfresco.service.cmr.repository.NodeRef; + +public interface ObjectFilter +{ + public boolean filter(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java b/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java new file mode 100644 index 0000000000..7db5595f32 --- /dev/null +++ b/source/java/org/alfresco/opencmis/PassthroughObjectFilter.java @@ -0,0 +1,13 @@ +package org.alfresco.opencmis; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class PassthroughObjectFilter implements ObjectFilter +{ + @Override + public boolean filter(NodeRef nodeRef) + { + return false; + } + +} diff --git a/source/java/org/alfresco/opencmis/PathObjectFilter.java b/source/java/org/alfresco/opencmis/PathObjectFilter.java new file mode 100644 index 0000000000..c84e85a5d9 --- /dev/null +++ b/source/java/org/alfresco/opencmis/PathObjectFilter.java @@ -0,0 +1,61 @@ +package org.alfresco.opencmis; + +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.NamespaceService; + +public class PathObjectFilter implements ObjectFilter +{ + private String rootPath; + private NodeService nodeService; + private NamespaceService namespaceService; + + private List excludedPaths; + + public void setExcludedPaths(List excludedPaths) + { + this.excludedPaths = excludedPaths; + } + + /** + * Sets the root path. + * + * @param path + * path within default store + */ + public void setRootPath(String path) + { + rootPath = path; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + @Override + public boolean filter(NodeRef nodeRef) + { + Path path = nodeService.getPath(nodeRef); + String s = path.toPrefixString(this.namespaceService); + return filter(s); + } + + public boolean filter(String path) + { + if(path.startsWith(rootPath)) + { + path = path.substring(rootPath.length()); + } + return excludedPaths.contains(path); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java index abef20edee..8dddefb4e1 100644 --- a/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java @@ -58,6 +58,19 @@ public abstract class AbstractProperty implements CMISPropertyAccessor this.propertyName = propertyName; } + protected String getGuid(String nodeId) + { + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } + } + /** * @return service registry */ diff --git a/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java index 814f5c8b49..6996813391 100644 --- a/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java @@ -55,9 +55,10 @@ public class AllowedChildObjectTypeIdsProperty extends AbstractProperty { if(nodeInfo.getType() == null) { - return (Serializable) Collections.emptyList(); + //If the type is null, we can't handle it so return an empty list + return (Serializable) Collections.emptyList(); } - + TypeDefinition type = getServiceRegistry().getDictionaryService() .getType(nodeInfo.getType().getAlfrescoClass()); if ((type != null) && (type.getChildAssociations() != null) && (!type.getChildAssociations().isEmpty())) diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java index 5c13d39030..410976d607 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java @@ -49,6 +49,6 @@ public class VersionSeriesCheckedOutIdProperty extends AbstractProperty return null; } - return nodeInfo.getCurrentNodeId() + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; + return getGuid(nodeInfo.getCurrentNodeId()) + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; } } diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java index a97dfa2855..4f6a745a27 100644 --- a/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java @@ -37,10 +37,11 @@ public class VersionSeriesIdProperty extends AbstractProperty { super(serviceRegistry, connector, PropertyIds.VERSION_SERIES_ID); } - + @Override public Serializable getValueInternal(CMISNodeInfo nodeInfo) { - return nodeInfo.getCurrentNodeId(); + return getGuid(nodeInfo.getCurrentNodeId()); + //return nodeInfo.getCurrentObjectId(); } } diff --git a/source/java/org/alfresco/opencmis/search/QueryTest.java b/source/java/org/alfresco/opencmis/search/QueryTest.java index e5794c4d8f..984edcc106 100644 --- a/source/java/org/alfresco/opencmis/search/QueryTest.java +++ b/source/java/org/alfresco/opencmis/search/QueryTest.java @@ -232,7 +232,11 @@ public class QueryTest extends BaseCMISTest public void setUp() throws Exception { super.setUp(); - + + cmisConnector.destroy(); // clean cached NodeRefs + cmisConnector.setStore(storeRef.toString()); + cmisConnector.setRootPath("/"); + // If FTS kicks in at the wrong moment, it can skew the test results. Temporarily disable it during the test this.luceneFTS.pause(); @@ -716,7 +720,7 @@ public class QueryTest extends BaseCMISTest } rs.close(); - options = new CMISQueryOptions("SELECT * FROM cmis:folder where cmis:parentId = '" + f8.toString() + "'", rootNodeRef.getStoreRef()); + options = new CMISQueryOptions("SELECT * FROM cmis:folder where cmis:parentId = '" + f8.getId() + "'", rootNodeRef.getStoreRef()); options.setDefaultFTSConnective(Connective.OR); options.setDefaultFTSFieldConnective(Connective.OR); rs = cmisQueryService.query(options); @@ -726,7 +730,7 @@ public class QueryTest extends BaseCMISTest Serializable sValue = row.getValue("cmis:parentId"); String value = DefaultTypeConverter.INSTANCE.convert(String.class, sValue); assertNotNull(value); - assertEquals(f8.toString(), value); + assertEquals(f8.getId(), value); CMISResultSetColumn column = rs.getResultSetMetaData().getColumn("cmis:parentId"); assertEquals(PropertyType.ID, column.getCMISDataType()); assertEquals(Cardinality.SINGLE, column.getCMISPropertyDefinition().getPropertyDefinition().getCardinality()); @@ -734,31 +738,31 @@ public class QueryTest extends BaseCMISTest } rs.close(); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId = '" + base.toString() + "'", 4, false, "cmis:parentId", new String(), false); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId <> '" + base.toString() + "'", folder_count-4, false, "cmis:parentId", new String(), false); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId < '" + base.toString() + "'", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId <= '" + base.toString() + "'", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId > '" + base.toString() + "'", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId >= '" + base.toString() + "'", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId = '" + base.getId() + "'", 4, false, "cmis:parentId", new String(), false); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId <> '" + base.getId() + "'", folder_count-4, false, "cmis:parentId", new String(), false); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId < '" + base.getId() + "'", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId <= '" + base.getId() + "'", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId > '" + base.getId() + "'", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId >= '" + base.getId() + "'", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId IN ('" + base.toString() + "')", 4, false, "cmis:parentId", new String(), false); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId NOT IN ('" + base.toString() + "')", folder_count-4, false, "cmis:parentId", new String(), false); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId IN ('" + base.getId() + "')", 4, false, "cmis:parentId", new String(), false); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId NOT IN ('" + base.getId() + "')", folder_count-4, false, "cmis:parentId", new String(), false); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId LIKE '" + base.toString() + "'", 4, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId NOT LIKE '" + base.toString() + "'", folder_count-4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId LIKE '" + base.getId() + "'", 4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId NOT LIKE '" + base.getId() + "'", folder_count-4, false, "cmis:parentId", new String(), true); testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId IS NOT NULL", folder_count, false, "cmis:parentId", new String(), false); testQuery("SELECT cmis:parentId FROM cmis:folder WHERE cmis:parentId IS NULL", 0, false, "cmis:parentId", new String(), false); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' = ANY cmis:parentId", 4, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' <> ANY cmis:parentId", folder_count-4, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' < ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' <= ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' > ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.getId() + "' = ANY cmis:parentId", 4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.getId() + "' <> ANY cmis:parentId", folder_count-4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.getId() + "' < ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.getId() + "' <= ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.getId() + "' > ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); testQuery("SELECT cmis:parentId FROM cmis:folder WHERE '" + base.toString() + "' >= ANY cmis:parentId", 0, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE ANY cmis:parentId IN ('" + base.toString() + "')", 4, false, "cmis:parentId", new String(), true); - testQuery("SELECT cmis:parentId FROM cmis:folder WHERE ANY cmis:parentId NOT IN ('" + base.toString() + "')", folder_count-4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE ANY cmis:parentId IN ('" + base.getId() + "')", 4, false, "cmis:parentId", new String(), true); + testQuery("SELECT cmis:parentId FROM cmis:folder WHERE ANY cmis:parentId NOT IN ('" + base.getId() + "')", folder_count-4, false, "cmis:parentId", new String(), true); } public void test_PATH() throws Exception @@ -1287,7 +1291,7 @@ public class QueryTest extends BaseCMISTest Serializable sValue = row.getValue("cmis:versionSeriesId"); String value = DefaultTypeConverter.INSTANCE.convert(String.class, sValue); assertNotNull(value); - assertEquals(row.getNodeRef().toString(), value); + assertEquals(row.getNodeRef().getId(), value); CMISResultSetColumn column = rs.getResultSetMetaData().getColumn("cmis:versionSeriesId"); assertNotNull(column); assertEquals(PropertyType.ID, column.getCMISDataType()); @@ -1793,7 +1797,7 @@ public class QueryTest extends BaseCMISTest Date lmd0 = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(c0, ContentModel.PROP_MODIFIED)); String lmds0 = df.format(lmd0); - options = new CMISQueryOptions("SELECT * FROM cmis:document WHERE cmis:lastModificationDate = TIMESTAMP '" + lmds0 + "' and cmis:objectId = '" + c0.toString() + "'", + options = new CMISQueryOptions("SELECT * FROM cmis:document WHERE cmis:lastModificationDate = TIMESTAMP '" + lmds0 + "' and cmis:objectId = '" + c0.getId() + "'", rootNodeRef.getStoreRef()); options.setDefaultFTSConnective(Connective.OR); options.setDefaultFTSFieldConnective(Connective.OR); @@ -2141,7 +2145,7 @@ public class QueryTest extends BaseCMISTest Date cd0 = DefaultTypeConverter.INSTANCE.convert(Date.class, nodeService.getProperty(c0, ContentModel.PROP_CREATED)); String cds0 = df.format(cd0); - options = new CMISQueryOptions("SELECT * FROM cmis:document WHERE cmis:creationDate = TIMESTAMP '" + cds0 + "' and cmis:objectId = '" + c0.toString() + "'", rootNodeRef + options = new CMISQueryOptions("SELECT * FROM cmis:document WHERE cmis:creationDate = TIMESTAMP '" + cds0 + "' and cmis:objectId = '" + c0.getId() + "'", rootNodeRef .getStoreRef()); options.setDefaultFTSConnective(Connective.OR); options.setDefaultFTSFieldConnective(Connective.OR); @@ -2770,7 +2774,7 @@ public class QueryTest extends BaseCMISTest //String docId = testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:name = 'Alfresco Tutorial'", 1, false, "cmis:objectId", new String(), false); - String docId = c0.toString(); + String docId = c0.getId(); testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId = '" + docId + "'", 1, false, "cmis:objectId", new String(), false); testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId <> '" + docId + "'", doc_count-1, false, "cmis:objectId", new String(), false); @@ -2787,7 +2791,7 @@ public class QueryTest extends BaseCMISTest nodeService.setProperty(c0, ContentModel.PROP_VERSION_LABEL, "1.0"); //docId = testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:name = 'Alfresco Tutorial'", 1, false, "cmis:objectId", new String(), false); - docId = c0.toString()+";1.0"; + docId = c0.getId()+";1.0"; testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId = '" + docId + "'", 1, false, "cmis:objectId", new String(), false); testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId <> '" + docId + "'", doc_count-1, false, "cmis:objectId", new String(), false); @@ -2801,7 +2805,7 @@ public class QueryTest extends BaseCMISTest //docId = testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:name = 'Alfresco Tutorial'", 1, false, "cmis:objectId", new String(), false); // comes back as 1.0 ?? - docId = c0.toString()+";2.1"; + docId = c0.getId()+";2.1"; testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId = '" + docId + "'", 1, false, "cmis:objectId", new String(), false); testQuery("SELECT cmis:objectId FROM cmis:document WHERE cmis:objectId <> '" + docId + "'", doc_count-1, false, "cmis:objectId", new String(), false); @@ -3341,7 +3345,7 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM cmis:folder WHERE cmis:name = '" + Name + "'", 1, false, "cmis:objectId", new String(), false); testQuery("SELECT * FROM cmis:folder WHERE cmis:name = 'Folder 1'", 1, false, "cmis:objectId", new String(), false); - testQuery("SELECT * FROM cmis:folder WHERE cmis:parentId = '" + base.toString() + "'", 4, false, "cmis:objectId", new String(), false); + testQuery("SELECT * FROM cmis:folder WHERE cmis:parentId = '" + base.getId() + "'", 4, false, "cmis:objectId", new String(), false); testQuery("SELECT * FROM cmis:folder WHERE cmis:allowedChildObjectTypeIds = 'meep'", 0, false, "cmis:objectId", new String(), true); } diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index 905f705d6e..11709915a0 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -27,6 +27,8 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.activities.feed.cleanup.FeedCleaner; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; @@ -236,6 +238,38 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean return activityFeedEntries; } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getPagedUserFeedEntries(java.lang.String, java.lang.String, java.lang.String, boolean, boolean, java.util.Set, java.util.Set) + */ + public PagingResults getPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) + { + try + { + String currentUser = getCurrentUser(); + if (! ((currentUser == null) || + (authorityService.isAdminAuthority(currentUser)) || + (currentUser.equals(feedUserId)) || + (AuthenticationUtil.getSystemUserName().equals(this.tenantService.getBaseNameUser(currentUser))))) + { + throw new AccessDeniedException("Unable to get user feed entries for '" + feedUserId + "' - currently logged in as '" + currentUser +"'"); + } + + if (siteId != null) + { + siteId = tenantService.getName(siteId); + } + + PagingResults activityFeedEntries = feedDAO.selectPagedUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, pagingRequest); + return activityFeedEntries; + } + catch (SQLException se) + { + AlfrescoRuntimeException are = new AlfrescoRuntimeException("Unable to get user feed entries: " + se.getMessage()); + logger.error(are); + throw are; + } + } + public List getUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId) { return getUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, null, null, minFeedId); diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java index 77db299e16..175643d3e0 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -114,7 +114,7 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy { this.maxIdRange = maxIdRange; } - + public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; @@ -478,7 +478,7 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy try { // Since we are in post-commit, we do best-effort - int deletedCnt = feedDAO.deleteSiteFeedEntries(siteId); + int deletedCnt = feedDAO.deleteSiteFeedEntries(tenantService.getName(siteId)); if (logger.isDebugEnabled()) { diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index 1f5f4e8e36..68a04babfe 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -420,9 +420,9 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher return applyInternal(); } }, tenantDomain); - - report = report + "\n" + tenantReport + " (for tenant: " + tenantDomain + ")"; } + + report = report + "\n (also applied to " + tenants.size() + " tenants)"; return report; } diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java index caf729c02d..d6fb79e1c9 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java @@ -84,7 +84,7 @@ public class FileContentStore * the root under which files will be stored. The directory will be created if it does not exist. * @see FileContentStore#FileContentStore(File) */ - private FileContentStore(String rootDirectoryStr) + /*package*/ FileContentStore(String rootDirectoryStr) { this(new File(rootDirectoryStr)); } @@ -199,7 +199,7 @@ public class FileContentStore * @return Returns a new and unique file * @throws IOException if the file or parent directories couldn't be created */ - private File createNewFile() throws IOException + /*package*/ File createNewFile() throws IOException { String contentUrl = FileContentStore.createNewFileStoreUrl(); return createNewFile(contentUrl); @@ -221,7 +221,7 @@ public class FileContentStore * * @see #setReadOnly(boolean) */ - private File createNewFile(String newContentUrl) throws IOException + /*package*/ File createNewFile(String newContentUrl) throws IOException { if (readOnly) { @@ -299,7 +299,7 @@ public class FileContentStore * @return Returns the equivalent content URL * @throws Exception */ - private String makeContentUrl(File file) + /*package*/ String makeContentUrl(File file) { String path = file.getAbsolutePath(); // check if it belongs to this store @@ -336,7 +336,7 @@ public class FileContentStore * * @see #checkUrl(String) */ - private File makeFile(String contentUrl) + /*package*/ File makeFile(String contentUrl) { // take just the part after the protocol Pair urlParts = super.getContentUrlParts(contentUrl); diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java index e4ba84bbf7..7fa6649229 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java @@ -22,6 +22,9 @@ import java.sql.SQLException; import java.util.Date; import java.util.List; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; + /** * Interface for activity feed DAO service */ @@ -53,4 +56,6 @@ public interface ActivityFeedDAO extends ActivitiesDAO public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; + + public PagingResults selectPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException; } diff --git a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java index 02ab775ad0..d8dcd93287 100644 --- a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java @@ -24,9 +24,14 @@ import java.util.Date; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.EmptyPagingResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.domain.activities.ActivityFeedQueryEntity; +import org.alfresco.util.Pair; import org.apache.ibatis.session.RowBounds; public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFeedDAO @@ -196,6 +201,129 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); } + private PagingResults getPagingResults(PagingRequest pagingRequest, final List feedEntries) + { + int maxItems = pagingRequest.getMaxItems(); + final boolean hasMoreItems = feedEntries.size() > maxItems; + if(hasMoreItems) + { + feedEntries.remove(feedEntries.size() - 1); + } + + return new PagingResults() + { + @Override + public List getPage() + { + return feedEntries; + } + + @Override + public boolean hasMoreItems() + { + return hasMoreItems; + } + + @Override + public Pair getTotalResultCount() + { + return new Pair(null, null); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } + + @SuppressWarnings("unchecked") + public PagingResults selectPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException + { + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); + params.setFeedUserId(feedUserId); + params.setActivitySummaryFormat(format); + + if (minFeedId > -1) + { + params.setMinId(minFeedId); + } + + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + + RowBounds rowBounds = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? + new RowBounds(skipCount, RowBounds.NO_ROW_LIMIT) : + new RowBounds(skipCount, maxItems + 1)); // +1 to check for more items + + if (siteId != null) + { + // given site + params.setSiteNetwork(siteId); + + if (excludeThisUser && excludeOtherUsers) + { + // effectively NOOP - return empty feed + return new EmptyPagingResults(); + } + if ((!excludeThisUser) && (!excludeOtherUsers)) + { + // no excludes => everyone => where feed user is me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_and_site", params, rowBounds)); + } + else if ((excludeThisUser) && (!excludeOtherUsers)) + { + // exclude feed user => others => where feed user is me and post user is not me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params, rowBounds)); + } + else if ((excludeOtherUsers) && (!excludeThisUser)) + { + // exclude others => me => where feed user is me and post user is me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params, rowBounds)); + } + } + else + { + // all sites + + if (excludeThisUser && excludeOtherUsers) + { + // effectively NOOP - return empty feed + return new EmptyPagingResults(); + } + if (!excludeThisUser && !excludeOtherUsers) + { + // no excludes => everyone => where feed user is me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser", params, rowBounds)); + } + else if (excludeThisUser) + { + // exclude feed user => others => where feed user is me and post user is not me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others", params, rowBounds)); + } + else if (excludeOtherUsers) + { + // exclude others => me => where feed user is me and post user is me + return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me", params, rowBounds)); + } + } + + // belts-and-braces + throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); + } + + @SuppressWarnings("unchecked") + public Long countSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException + { + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); + params.setSiteNetwork(siteId); + params.setActivitySummaryFormat(format); + + // for given site + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_site", params); + } + @SuppressWarnings("unchecked") @Override public List selectSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException diff --git a/source/java/org/alfresco/repo/forum/CommentService.java b/source/java/org/alfresco/repo/forum/CommentService.java index fd4d0f0d9b..8d05a6b5b7 100644 --- a/source/java/org/alfresco/repo/forum/CommentService.java +++ b/source/java/org/alfresco/repo/forum/CommentService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,12 +20,12 @@ package org.alfresco.repo.forum; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ForumModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.service.cmr.repository.NodeRef; /** - * This is a starting point for a future service for handling Share comments. - *

- * This class may change in the future as requirements become clearer. + * A service for handling comments. * * @author Neil Mc Erlean * @since 4.0 @@ -50,4 +50,39 @@ public interface CommentService * @return the fm:topic NodeRef, if one exists, else null. */ NodeRef getShareCommentsTopic(NodeRef discussableNode); + + /** + * Creates a comment for the discussableNode + * + * @param discussableNode the node in Share which is being commented on . + * @param title - title of the comment + * @param comment - body of the comment + * @param suppressRollups - should it suppressRollups + * @return NodeRef - the created node reference + */ + NodeRef createComment(NodeRef discussableNode, String title, String comment, boolean suppressRollups); + + /** + * Updates the comment + * + * @param nodeRef the comment node. + * @param title - title of the comment + * @param comment - body of the comment + */ + void updateComment(NodeRef commentNodeRef, String title, String comment); + + /** + * Returns a paged list of comments. + * + * @param nodeRef the node which is being commented on . + * @return a list of comment nodes + */ + PagingResults listComments(NodeRef discussableNode, PagingRequest paging); + + /** + * Deletes the comment for the discussableNode + * + * @param commentNodeRef the node in Share which is being commented on. + */ + void deleteComment(NodeRef commentNodeRef); } diff --git a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java index 07fa8bedbc..7e69dc0af6 100644 --- a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java +++ b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java @@ -18,36 +18,94 @@ */ package org.alfresco.repo.forum; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.query.CannedQueryFactory; +import org.alfresco.query.CannedQueryResults; +import org.alfresco.query.EmptyPagingResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQueryFactory; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.registry.NamedObjectRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONObject; /** * @author Neil Mc Erlean * @since 4.0 */ +// TODO consolidate this and ScriptCommentService and the implementations of comments.* web scripts. public class CommentServiceImpl implements CommentService { + private static Log logger = LogFactory.getLog(CommentServiceImpl.class); + /** * Naming convention for Share comment model. fm:forum contains fm:topic */ private static final QName FORUM_TO_TOPIC_ASSOC_QNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Comments"); + private static final String COMMENTS_TOPIC_NAME = "Comments"; + + private static final String CANNED_QUERY_GET_CHILDREN = "commentsGetChildrenCannedQueryFactory"; // Injected services private NodeService nodeService; + private ContentService contentService; + private ActivityService activityService; + private SiteService siteService; - public void setNodeService(NodeService nodeService) + private NamedObjectRegistry> cannedQueryRegistry; + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } - @Override + public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) + { + this.cannedQueryRegistry = cannedQueryRegistry; + } + + @Override public NodeRef getDiscussableAncestor(NodeRef descendantNodeRef) { // For "Share comments" i.e. fm:post nodes created via the Share commenting UI, the containment structure is as follows: @@ -85,6 +143,29 @@ public class CommentServiceImpl implements CommentService return result; } + + @Override + public PagingResults listComments(NodeRef discussableNode, PagingRequest paging) + { + NodeRef commentsFolder = getShareCommentsTopic(discussableNode); + if(commentsFolder != null) + { + List> sort = new ArrayList>(); + sort.add(new Pair(ContentModel.PROP_CREATED, false)); + + // Run the canned query + GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_GET_CHILDREN); + GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(commentsFolder, null, null, null, null, sort, paging); + + // Execute the canned query + CannedQueryResults results = cq.execute(); + return results; + } + else + { + return new EmptyPagingResults(); + } + } @Override public NodeRef getShareCommentsTopic(NodeRef discussableNode) @@ -114,4 +195,217 @@ public class CommentServiceImpl implements CommentService return result; } + +// private ScriptNode createCommentsFolder(ScriptNode node) +// { +// final NodeRef nodeRef = node.getNodeRef(); +// +// NodeRef commentsFolder = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() +// { +// public NodeRef doWork() throws Exception +// { +// NodeRef commentsFolder = null; +// +// // ALF-5240: turn off auditing round the discussion node creation to prevent +// // the source document from being modified by the first user leaving a comment +// behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); +// +// try +// { +// nodeService.addAspect(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussable"), null); +// List assocs = nodeService.getChildAssocs(nodeRef, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"), RegexQNamePattern.MATCH_ALL); +// if (assocs.size() != 0) +// { +// NodeRef forumFolder = assocs.get(0).getChildRef(); +// +// Map props = new HashMap(1, 1.0f); +// props.put(ContentModel.PROP_NAME, COMMENTS_TOPIC_NAME); +// commentsFolder = nodeService.createNode( +// forumFolder, +// ContentModel.ASSOC_CONTAINS, +// QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, COMMENTS_TOPIC_NAME), +// QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "topic"), +// props).getChildRef(); +// } +// } +// finally +// { +// behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); +// } +// +// return commentsFolder; +// } +// +// }, AuthenticationUtil.getSystemUserName()); +// +// return new ScriptNode(commentsFolder, serviceRegistry, getScope()); +// } + + private String getSiteId(final NodeRef nodeRef) + { + String siteId = AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public String doWork() throws Exception + { + String siteId = null; + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + siteId = siteInfo.getShortName(); + } + return siteId; + } + }); + + return siteId; + } + + @SuppressWarnings("unchecked") + private JSONObject getActivityData(String siteId, final NodeRef nodeRef) + { + if(siteId != null) + { + // create an activity (with some Share-specific parts) + JSONObject json = new JSONObject(); + json.put("title", nodeService.getProperty(nodeRef, ContentModel.PROP_NAME)); + try + { + StringBuilder sb = new StringBuilder("document-details?nodeRef="); + sb.append(URLEncoder.encode(nodeRef.toString(), "UTF-8")); + json.put("page", sb.toString()); + } + catch (UnsupportedEncodingException e) + { + logger.warn("Unable to urlencode page for create comment activity"); + } + + return json; + } + else + { + logger.warn("Unable to determine site in which node " + nodeRef + " resides."); + return null; + } + } + + private void postActivity(String siteId, String activityType, JSONObject activityData) + { + if(activityData != null) + { + activityService.postActivity(activityType, siteId, "comments", activityData.toString()); + } + } + + @Override + public NodeRef createComment(final NodeRef discussableNode, String title, String comment, boolean suppressRollups) + { + if(comment == null) + { + throw new IllegalArgumentException("Must provide a non-null comment"); + } + + // There is no CommentService, so we have to create the node structure by hand. + // This is what happens within e.g. comment.put.json.js when comments are submitted via the REST API. + if (!nodeService.hasAspect(discussableNode, ForumModel.ASPECT_DISCUSSABLE)) + { + nodeService.addAspect(discussableNode, ForumModel.ASPECT_DISCUSSABLE, null); + } + if (!nodeService.hasAspect(discussableNode, ForumModel.ASPECT_COMMENTS_ROLLUP) && !suppressRollups) + { + nodeService.addAspect(discussableNode, ForumModel.ASPECT_COMMENTS_ROLLUP, null); + } + // Forum node is created automatically by DiscussableAspect behaviour. + NodeRef forumNode = nodeService.getChildAssocs(discussableNode, ForumModel.ASSOC_DISCUSSION, QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion")).get(0).getChildRef(); + + final List existingTopics = nodeService.getChildAssocs(forumNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Comments")); + NodeRef topicNode = null; + if (existingTopics.isEmpty()) + { + Map props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_NAME, COMMENTS_TOPIC_NAME); + topicNode = nodeService.createNode(forumNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Comments"), ForumModel.TYPE_TOPIC, props).getChildRef(); + } + else + { + topicNode = existingTopics.get(0).getChildRef(); + } + + NodeRef postNode = nodeService.createNode(topicNode, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS, ForumModel.TYPE_POST).getChildRef(); + nodeService.setProperty(postNode, ContentModel.PROP_CONTENT, new ContentData(null, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null)); + nodeService.setProperty(postNode, ContentModel.PROP_TITLE, title); + ContentWriter writer = contentService.getWriter(postNode, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_HTML); + writer.setEncoding("UTF-8"); + writer.putContent(comment); + + // determine the siteId and activity data of the comment NodeRef + String siteId = getSiteId(discussableNode); + JSONObject activityData = getActivityData(siteId, discussableNode); + + postActivity(siteId, "org.alfresco.comments.comment-created", activityData); + + return postNode; + } + + public void updateComment(NodeRef commentNodeRef, String title, String comment) + { + QName nodeType = nodeService.getType(commentNodeRef); + if(!nodeType.equals(ForumModel.TYPE_POST)) + { + throw new IllegalArgumentException("Node to update is not a comment node."); + } + + ContentWriter writer = contentService.getWriter(commentNodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_HTML); // TODO should this be set by the caller? + writer.putContent(comment); + + if(title != null) + { + nodeService.setProperty(commentNodeRef, ContentModel.PROP_TITLE, title); + } + + // determine the siteId and activity data of the comment NodeRef + String siteId = getSiteId(commentNodeRef); + NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef); + if(discussableNodeRef != null) + { + JSONObject activityData = getActivityData(siteId, discussableNodeRef); + + postActivity(siteId, "org.alfresco.comments.comment-updated", activityData); + } + else + { + logger.warn("Unable to determine discussable node for the comment with nodeRef " + commentNodeRef + ", not posting an activity"); + } + } + + public void deleteComment(NodeRef commentNodeRef) + { + QName nodeType = nodeService.getType(commentNodeRef); + if(!nodeType.equals(ForumModel.TYPE_POST)) + { + throw new IllegalArgumentException("Node to delete is not a comment node."); + } + + // determine the siteId and activity data of the comment NodeRef (do this before removing the comment NodeRef) + String siteId = getSiteId(commentNodeRef); + NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef); + JSONObject activityData = null; + if(discussableNodeRef != null) + { + activityData = getActivityData(siteId, discussableNodeRef); + } + + nodeService.deleteNode(commentNodeRef); + + if(activityData != null) + { + postActivity(siteId, "org.alfresco.comments.comment-deleted", activityData); + } + else + { + logger.warn("Unable to determine discussable node for the comment with nodeRef " + commentNodeRef + ", not posting an activity"); + } + } } diff --git a/source/java/org/alfresco/repo/importer/DefaultImporterContentCache.java b/source/java/org/alfresco/repo/importer/DefaultImporterContentCache.java new file mode 100644 index 0000000000..dad9661c62 --- /dev/null +++ b/source/java/org/alfresco/repo/importer/DefaultImporterContentCache.java @@ -0,0 +1,107 @@ +/* + * 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.importer; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.view.ImportPackageHandler; +import org.alfresco.service.cmr.view.ImporterContentCache; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +public class DefaultImporterContentCache implements ImporterContentCache +{ + private static final Log logger = LogFactory.getLog(DefaultImporterContentCache.class); + + private ContentService contentService; + private Map contentUrls = new HashMap(); + final private ReadWriteLock contentUrlsLock = new ReentrantReadWriteLock(); + + public void setContentService(ContentService service) + { + contentService = service; + } + + @Override + public ContentData getContent(final ImportPackageHandler handler, final ContentData sourceContentData) + { + ContentData cachedContentData = null; + final String sourceContentUrl = sourceContentData.getContentUrl(); + + contentUrlsLock.readLock().lock(); + + try + { + cachedContentData = contentUrls.get(sourceContentUrl); + if (cachedContentData == null) + { + contentUrlsLock.readLock().unlock(); + contentUrlsLock.writeLock().lock(); + + try + { + cachedContentData = contentUrls.get(sourceContentUrl); + if (cachedContentData == null) + { + cachedContentData = TenantUtil.runAsTenant(new TenantRunAsWork() + { + @Override + public ContentData doWork() throws Exception + { + InputStream contentStream = handler.importStream(sourceContentUrl); + ContentWriter writer = contentService.getWriter(null, null, false); + writer.setEncoding(sourceContentData.getEncoding()); + writer.setMimetype(sourceContentData.getMimetype()); + writer.putContent(contentStream); + return writer.getContentData(); + } + }, TenantService.DEFAULT_DOMAIN); + + contentUrls.put(sourceContentUrl, cachedContentData); + } + } + finally + { + contentUrlsLock.readLock().lock(); + contentUrlsLock.writeLock().unlock(); + } + } + } + finally + { + contentUrlsLock.readLock().unlock(); + } + + if (logger.isDebugEnabled()) + logger.debug("Mapped contentUrl " + sourceContentUrl + " to " + cachedContentData); + + return cachedContentData; + } +} diff --git a/source/java/org/alfresco/repo/importer/ExportSourceImporter.java b/source/java/org/alfresco/repo/importer/ExportSourceImporter.java index 7a809eedc2..d0fea88c3a 100644 --- a/source/java/org/alfresco/repo/importer/ExportSourceImporter.java +++ b/source/java/org/alfresco/repo/importer/ExportSourceImporter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,7 +33,6 @@ import java.util.Set; import javax.transaction.UserTransaction; import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.NamespacePrefixResolver; @@ -236,27 +236,35 @@ public class ExportSourceImporter implements ImporterJobSPI private static ImporterBinding REPLACE_BINDING = new ImporterBinding() { - + @Override public UUID_BINDING getUUIDBinding() { return UUID_BINDING.UPDATE_EXISTING; } + @Override public String getValue(String key) { return null; } + @Override public boolean allowReferenceWithinTransaction() { return false; } + @Override public QName[] getExcludedClasses() { return null; } + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } }; } diff --git a/source/java/org/alfresco/repo/importer/FileSourceImporter.java b/source/java/org/alfresco/repo/importer/FileSourceImporter.java index 1b63c595c4..b101fde205 100644 --- a/source/java/org/alfresco/repo/importer/FileSourceImporter.java +++ b/source/java/org/alfresco/repo/importer/FileSourceImporter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -34,6 +34,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.NamespacePrefixResolver; @@ -209,27 +210,35 @@ public class FileSourceImporter implements ImporterJobSPI private static ImporterBinding REPLACE_BINDING = new ImporterBinding() { - + @Override public UUID_BINDING getUUIDBinding() { return UUID_BINDING.UPDATE_EXISTING; } + @Override public String getValue(String key) { return null; } + @Override public boolean allowReferenceWithinTransaction() { return false; } + @Override public QName[] getExcludedClasses() { return null; } - + + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } }; } diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index cdfb3ba7ec..e4f43774ac 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -43,6 +43,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.cmr.view.ImporterProgress; import org.alfresco.service.cmr.view.ImporterService; @@ -654,31 +655,35 @@ public class ImporterBootstrap extends AbstractLifecycleBean return value; } + @Override public UUID_BINDING getUUIDBinding() { return uuidBinding; } - /** - * Allow bootstrap to override default Node UUID Binding - * - * @param uuidBinding UUID_BINDING - */ private void setUUIDBinding(UUID_BINDING uuidBinding) { this.uuidBinding = uuidBinding; } + @Override public boolean allowReferenceWithinTransaction() { return true; } + @Override public QName[] getExcludedClasses() { // Note: Do not exclude any classes, we want to import all return new QName[] {}; } + + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } } /** diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index 4f1c3c859f..a3cba3d057 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -66,6 +66,7 @@ import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ImportPackageHandler; import org.alfresco.service.cmr.view.ImporterBinding; import org.alfresco.service.cmr.view.ImporterBinding.UUID_BINDING; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.cmr.view.ImporterProgress; import org.alfresco.service.cmr.view.ImporterService; @@ -763,6 +764,8 @@ public class ImporterComponent implements ImporterService */ private void importContent(NodeRef nodeRef, QName propertyName, String importContentData) { + ImporterContentCache contentCache = (binding == null) ? null : binding.getImportConentCache(); + // bind import content data description importContentData = bindPlaceHolder(importContentData, binding); if (importContentData != null && importContentData.length() > 0) @@ -772,26 +775,35 @@ public class ImporterComponent implements ImporterService String contentUrl = contentData.getContentUrl(); if (contentUrl != null && contentUrl.length() > 0) { - // import the content from the url - InputStream contentStream = streamHandler.importStream(contentUrl); - ContentWriter writer = contentService.getWriter(nodeRef, propertyName, true); - writer.setEncoding(contentData.getEncoding()); - writer.setMimetype(contentData.getMimetype()); - - Map propsBefore = null; - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + if (contentCache != null) { - propsBefore = nodeService.getProperties(nodeRef); + // import content from source + ContentData cachedContentData = contentCache.getContent(streamHandler, contentData); + nodeService.setProperty(nodeRef, propertyName, cachedContentData); } - - writer.putContent(contentStream); - - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + else { - // Since behaviours for content nodes have all been disabled, - // it is necessary to update the user's usage stats. - Map propsAfter = nodeService.getProperties(nodeRef); - contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); + // import the content from the url + InputStream contentStream = streamHandler.importStream(contentUrl); + ContentWriter writer = contentService.getWriter(nodeRef, propertyName, true); + writer.setEncoding(contentData.getEncoding()); + writer.setMimetype(contentData.getMimetype()); + + Map propsBefore = null; + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + propsBefore = nodeService.getProperties(nodeRef); + } + + writer.putContent(contentStream); + + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + // Since behaviours for content nodes have all been disabled, + // it is necessary to update the user's usage stats. + Map propsAfter = nodeService.getProperties(nodeRef); + contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); + } } reportContentCreated(nodeRef, contentUrl); diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 94337bc48a..1ff7e34488 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -1411,47 +1411,47 @@ public class FileFolderServiceImplTest extends TestCase public void testList_HiddenFiles() { - // Test that hidden files are not returned for clients that should not be able to see them, - // and that the total result count is correct. + // Test that hidden files are not returned for clients that should not be able to see them, + // and that the total result count is correct. - Client saveClient = FileFilterMode.setClient(Client.webdav); - try - { - // create some hidden files - NodeRef nodeRef = fileFolderService.create(workingRootNodeRef, "" + System.currentTimeMillis(), ContentModel.TYPE_CONTENT).getNodeRef(); - NodeRef nodeRef1 = fileFolderService.create(nodeRef, "parent", ContentModel.TYPE_CONTENT).getNodeRef(); - for(int i = 0; i < 10; i++) - { - fileFolderService.create(nodeRef1, ".child" + i, ContentModel.TYPE_CONTENT).getNodeRef(); - } - - // and some visible files - for(int i = 0; i < 10; i++) - { - fileFolderService.create(nodeRef1, "visiblechild" + i, ContentModel.TYPE_CONTENT).getNodeRef(); - } + Client saveClient = FileFilterMode.setClient(Client.webdav); + try + { + // create some hidden files + NodeRef nodeRef = fileFolderService.create(workingRootNodeRef, "" + System.currentTimeMillis(), ContentModel.TYPE_CONTENT).getNodeRef(); + NodeRef nodeRef1 = fileFolderService.create(nodeRef, "parent", ContentModel.TYPE_CONTENT).getNodeRef(); + for(int i = 0; i < 10; i++) + { + fileFolderService.create(nodeRef1, ".child" + i, ContentModel.TYPE_CONTENT).getNodeRef(); + } + + // and some visible files + for(int i = 0; i < 10; i++) + { + fileFolderService.create(nodeRef1, "visiblechild" + i, ContentModel.TYPE_CONTENT).getNodeRef(); + } - // switch to a client that should not see the hidden files - saveClient = FileFilterMode.setClient(Client.cmis); - PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE); - pagingRequest.setRequestTotalCountMax(10000); // need this so that total count is set + // switch to a client that should not see the hidden files + saveClient = FileFilterMode.setClient(Client.cmis); + PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE); + pagingRequest.setRequestTotalCountMax(10000); // need this so that total count is set - PagingResults results = fileFolderService.list(nodeRef1, true, true, null, null, pagingRequest); - Pair totalResultCount = results.getTotalResultCount(); - assertNotNull(totalResultCount.getFirst()); - assertEquals("Total result lower count should be 10", 10, totalResultCount.getFirst().intValue()); - assertNotNull(totalResultCount.getSecond()); - assertEquals("Total result upper count should be 10", 10, totalResultCount.getSecond().intValue()); - for(FileInfo fileInfo : results.getPage()) - { - assertTrue(fileInfo.getName().startsWith("visiblechild")); - } - assertEquals("Expected only 10 results", 10, results.getPage().size()); - } - finally - { - FileFilterMode.setClient(saveClient); - } + PagingResults results = fileFolderService.list(nodeRef1, true, true, null, null, pagingRequest); + Pair totalResultCount = results.getTotalResultCount(); + assertNotNull(totalResultCount.getFirst()); + assertEquals("Total result lower count should be 10", 10, totalResultCount.getFirst().intValue()); + assertNotNull(totalResultCount.getSecond()); + assertEquals("Total result upper count should be 10", 10, totalResultCount.getSecond().intValue()); + for(FileInfo fileInfo : results.getPage()) + { + assertTrue(fileInfo.getName().startsWith("visiblechild")); + } + assertEquals("Expected only 10 results", 10, results.getPage().size()); + } + finally + { + FileFilterMode.setClient(saveClient); + } } public void testList_notCheckedOut_ALF_13602() diff --git a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java index c5d4ca0959..cbe9c830e8 100644 --- a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java +++ b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java @@ -19,14 +19,22 @@ package org.alfresco.repo.preference; import java.io.Serializable; +import java.text.Collator; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -41,6 +49,9 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.Pair; import org.json.JSONException; import org.json.JSONObject; @@ -51,6 +62,9 @@ import org.json.JSONObject; */ public class PreferenceServiceImpl implements PreferenceService { + private static final String FAVOURITE_SITES_PREFIX = "org.alfresco.share.sites.favourites."; + private static final int FAVOURITE_SITES_PREFIX_LENGTH = FAVOURITE_SITES_PREFIX.length(); + /** Node service */ private NodeService nodeService; @@ -60,6 +74,9 @@ public class PreferenceServiceImpl implements PreferenceService /** Person service */ private PersonService personService; + /** Site service */ + private SiteService siteService; + /** Permission Service */ private PermissionService permissionService; @@ -90,6 +107,16 @@ public class PreferenceServiceImpl implements PreferenceService } /** + * Set the site service + * + * @param siteService the site service + */ + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + /** * Set the person service * * @param personService the person service @@ -136,13 +163,61 @@ public class PreferenceServiceImpl implements PreferenceService } /** - * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, + * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, java.lang.String) * java.lang.String) */ @SuppressWarnings("unchecked") public Map getPreferences(String userName, String preferenceFilter) { - Map preferences = new HashMap(20); + Map preferences = new TreeMap(); + + try + { + JSONObject jsonPrefs = getPreferencesObject(userName); + if(jsonPrefs != null) + { + // Build hash from preferences stored in the repository + Iterator keys = jsonPrefs.keys(); + while (keys.hasNext()) + { + String key = (String)keys.next(); + + if (preferenceFilter == null || + preferenceFilter.length() == 0 || + matchPreferenceNames(key, preferenceFilter) == true) + { + preferences.put(key, (Serializable)jsonPrefs.get(key)); + } + } + } + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + + return preferences; + } + + private PageDetails getPageDetails(PagingRequest pagingRequest, int totalSize) + { + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + int end = maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : skipCount + maxItems; + int pageSize = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : maxItems); + if(pageSize > totalSize - skipCount) + { + pageSize = totalSize - skipCount; + } + + boolean hasMoreItems = end < totalSize; + + return new PageDetails(pageSize, hasMoreItems, skipCount, maxItems, end); + } + + private JSONObject getPreferencesObject(String userName) throws JSONException + { + JSONObject jsonPrefs = null; // Get the user node reference NodeRef personNodeRef = this.personService.getPerson(userName); @@ -157,39 +232,17 @@ public class PreferenceServiceImpl implements PreferenceService authenticationContext.isSystemUserName(currentUserName) || authorityService.isAdminAuthority(currentUserName)) { - try + // Check for preferences aspect + if (this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) { - // Check for preferences aspect - if (this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) + // Get the preferences for this user + ContentReader reader = this.contentService.getReader(personNodeRef, + ContentModel.PROP_PREFERENCE_VALUES); + if (reader != null) { - // Get the preferences for this user - JSONObject jsonPrefs = new JSONObject(); - ContentReader reader = this.contentService.getReader(personNodeRef, - ContentModel.PROP_PREFERENCE_VALUES); - if (reader != null) - { - jsonPrefs = new JSONObject(reader.getContentString()); - } - - // Build hash from preferences stored in the repository - Iterator keys = jsonPrefs.keys(); - while (keys.hasNext()) - { - final String key = (String) keys.next(); - - if (preferenceFilter == null || preferenceFilter.length() == 0 || - matchPreferenceNames(key, preferenceFilter) == true) - { - preferences.put(key, (Serializable) jsonPrefs.get(key)); - } - } + jsonPrefs = new JSONObject(reader.getContentString()); } } - catch (JSONException exception) - { - throw new AlfrescoRuntimeException("Can not get preferences for " + userName - + " because there was an error pasing the JSON data.", exception); - } } else { @@ -198,8 +251,90 @@ public class PreferenceServiceImpl implements PreferenceService throw new UnauthorizedAccessException("The current user " + currentUserName + " does not have sufficient permissions to get the preferences of the user " + userName); } + + return jsonPrefs; + } - return preferences; + public Serializable getPreference(String userName, String preferenceName) + { + String preferenceValue = null; + + try + { + JSONObject jsonPrefs = getPreferencesObject(userName); + if(jsonPrefs != null) + { + if(jsonPrefs.has(preferenceName)) + { + preferenceValue = jsonPrefs.getString(preferenceName); + } + } + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + + return preferenceValue; + } + + public PagingResults> getPagedPreferences(String userName, String preferenceFilter, PagingRequest pagingRequest) + { + final Map prefs = getPreferences(userName, preferenceFilter); + + int totalSize = prefs.size(); + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + int end = maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : skipCount + maxItems; + int pageSize = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : Math.max(maxItems, totalSize - skipCount)); + final boolean hasMoreItems = end < totalSize; + + final List> page = new ArrayList>(pageSize); + Iterator> it = prefs.entrySet().iterator(); + for(int counter = 0; counter < end && it.hasNext(); counter++) + { + Map.Entry pref = it.next(); + + if(counter < skipCount) + { + continue; + } + + if(counter > end - 1) + { + break; + } + + page.add(new Pair(pref.getKey(), pref.getValue())); + } + + return new PagingResults>() + { + @Override + public List> getPage() + { + return page; + } + + @Override + public boolean hasMoreItems() + { + return hasMoreItems; + } + + @Override + public Pair getTotalResultCount() + { + Integer total = Integer.valueOf(prefs.size()); + return new Pair(total, total); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; } /** @@ -409,4 +544,170 @@ public class PreferenceServiceImpl implements PreferenceService authenticationContext.isSystemUserName(currentUserName) || permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED); } + public static class PageDetails + { + private boolean hasMoreItems = false; + private int pageSize; + private int skipCount; + private int maxItems; + private int end; + + public PageDetails(int pageSize, boolean hasMoreItems, int skipCount, int maxItems, int end) + { + super(); + this.hasMoreItems = hasMoreItems; + this.pageSize = pageSize; + this.skipCount = skipCount; + this.maxItems = maxItems; + this.end = end; + } + + public int getSkipCount() + { + return skipCount; + } + + public int getMaxItems() + { + return maxItems; + } + + public int getEnd() + { + return end; + } + + public boolean hasMoreItems() + { + return hasMoreItems; + } + + public int getPageSize() + { + return pageSize; + } + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#isFavouriteSite(java.lang.String, java.lang.String) + */ + public boolean isFavouriteSite(String userName, String siteShortName) + { + StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); + prefKey.append(siteShortName); + + String value = (String)getPreference(userName, prefKey.toString()); + return (value == null ? false : value.equalsIgnoreCase("true")); + } + + /** + * @see org.alfresco.service.cmr.preference.PreferenceService#addFavouriteSite(java.lang.String, java.lang.String) + */ + public void addFavouriteSite(String userName, String siteShortName) + { + StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); + prefKey.append(siteShortName); + + Map preferences = new HashMap(1); + preferences.put(prefKey.toString(), Boolean.TRUE); + setPreferences(userName, preferences); + } + + /** + * @see org.alfresco.service.cmr.preference.PreferenceService#removeFavouriteSite(java.lang.String, java.lang.String) + */ + public void removeFavouriteSite(String userName, String siteShortName) + { + StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); + prefKey.append(siteShortName); + + clearPreferences(userName, prefKey.toString()); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getFavouriteSites(java.lang.String, org.alfresco.query.PagingRequest) + */ + public PagingResults getFavouriteSites(String userName, PagingRequest pagingRequest) + { + final Collator collator = Collator.getInstance(); + + final Set sortedFavouriteSites = new TreeSet(new Comparator() + { + @Override + public int compare(SiteInfo o1, SiteInfo o2) + { + return collator.compare(o1.getTitle(), o2.getTitle()); + } + }); + + Map prefs = getPreferences(userName, FAVOURITE_SITES_PREFIX); + for(String key : prefs.keySet()) + { + boolean isFavourite = false; + Serializable s = prefs.get(key); + if(s instanceof Boolean) + { + isFavourite = (Boolean)s; + } + if(isFavourite) + { + String siteShortName = key.substring(FAVOURITE_SITES_PREFIX_LENGTH); + SiteInfo siteInfo = siteService.getSite(siteShortName); + if(siteInfo != null) + { + sortedFavouriteSites.add(siteInfo); + } + } + } + + int totalSize = sortedFavouriteSites.size(); + final PageDetails pageDetails = getPageDetails(pagingRequest, totalSize); + + final List page = new ArrayList(pageDetails.getPageSize()); + Iterator it = sortedFavouriteSites.iterator(); + for(int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) + { + SiteInfo favouriteSite = it.next(); + + if(counter < pageDetails.getSkipCount()) + { + continue; + } + + if(counter > pageDetails.getEnd() - 1) + { + break; + } + + page.add(favouriteSite); + } + + return new PagingResults() + { + @Override + public List getPage() + { + return page; + } + + @Override + public boolean hasMoreItems() + { + return pageDetails.hasMoreItems(); + } + + @Override + public Pair getTotalResultCount() + { + Integer total = Integer.valueOf(sortedFavouriteSites.size()); + return new Pair(total, total); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } } diff --git a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java index 5b37b8850a..254604b79a 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java @@ -403,7 +403,7 @@ public class RatingServiceImpl implements RatingService float totalRating = getTotalRating(targetNode, ratingSchemeName); int ratingCount = getRatingsCount(targetNode, ratingSchemeName); - return ratingCount == 0 ? -1f : totalRating / (float)ratingCount; + return ratingCount == 0 ? -1 : Float.valueOf(totalRating / (float)ratingCount); } public int getRatingsCount(NodeRef targetNode, String ratingSchemeName) diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryServiceImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryServiceImpl.java index c8503b52c3..f3fe54d7c4 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryServiceImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneCategoryServiceImpl.java @@ -30,6 +30,9 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.search.IndexerAndSearcher; import org.alfresco.repo.search.IndexerException; import org.alfresco.repo.tenant.TenantService; @@ -46,6 +49,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.CategoryService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; @@ -142,6 +146,11 @@ public class LuceneCategoryServiceImpl implements CategoryService } public Collection getChildren(NodeRef categoryRef, Mode mode, Depth depth) + { + return getChildren(categoryRef, mode, depth, false); + } + + private Collection getChildren(NodeRef categoryRef, Mode mode, Depth depth, boolean sortByName) { if (categoryRef == null) { @@ -191,7 +200,15 @@ public class LuceneCategoryServiceImpl implements CategoryService SearchService searcher = indexerAndSearcher.getSearcher(categoryRef.getStoreRef(), true); // Perform the search - resultSet = searcher.query(categoryRef.getStoreRef(), "lucene", luceneQuery.toString(), null); + SearchParameters searchParameters = new SearchParameters(); + searchParameters.setQuery(luceneQuery.toString()); + searchParameters.setLanguage("lucene"); + if(sortByName) + { + searchParameters.addSort("@" + ContentModel.PROP_NAME, true); + } + searchParameters.addStore(categoryRef.getStoreRef()); + resultSet = searcher.query(searchParameters); // Convert from search results to the required Child Assocs return resultSetToChildAssocCollection(resultSet); @@ -338,6 +355,68 @@ public class LuceneCategoryServiceImpl implements CategoryService throw new UnsupportedOperationException(); } + public PagingResults getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName) + { + final List assocs = new LinkedList(); + Set nodeRefs = getClassificationNodes(storeRef, aspectName); + + final int skipCount = pagingRequest.getSkipCount(); + final int maxItems = pagingRequest.getMaxItems(); + final int size = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : skipCount + maxItems); + int count = 0; + boolean moreItems = false; + + OUTER: for(NodeRef nodeRef : nodeRefs) + { + Collection children = getChildren(nodeRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE, sortByName); + for(ChildAssociationRef child : children) + { + count++; + + if(count <= skipCount) + { + continue; + } + + if(count > size) + { + moreItems = true; + break OUTER; + } + + assocs.add(child); + } + } + + final boolean hasMoreItems = moreItems; + return new PagingResults() + { + @Override + public List getPage() + { + return assocs; + } + + @Override + public boolean hasMoreItems() + { + return hasMoreItems; + } + + @Override + public Pair getTotalResultCount() + { + return new Pair(null, null); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } + public Collection getRootCategories(StoreRef storeRef, QName aspectName) { Collection assocs = new LinkedList(); diff --git a/source/java/org/alfresco/repo/search/impl/querymodel/impl/lucene/LuceneQueryEngine.java b/source/java/org/alfresco/repo/search/impl/querymodel/impl/lucene/LuceneQueryEngine.java index 9dd6c0bc21..3a208997cd 100644 --- a/source/java/org/alfresco/repo/search/impl/querymodel/impl/lucene/LuceneQueryEngine.java +++ b/source/java/org/alfresco/repo/search/impl/querymodel/impl/lucene/LuceneQueryEngine.java @@ -207,6 +207,7 @@ public class LuceneQueryEngine implements QueryEngine try { StoreRef storeRef = options.getStores().get(0); + searchParameters.addStore(storeRef); if (query instanceof LuceneQueryBuilder) { SearchService searchService = indexAndSearcher.getSearcher(storeRef, options.isIncludeInTransactionData()); diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 0b7c72a733..1c7efc5b36 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletResponse; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.httpclient.HttpClientFactory; +import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService; import org.alfresco.repo.admin.RepositoryState; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; @@ -95,6 +96,8 @@ public class SolrQueryHTTPClient implements BeanFactoryAware private HashMap httpClients = new HashMap(); private HashMap mappingLookup = new HashMap(); + + private String alternativeDictionary = CMISStrictDictionaryService.DEFAULT; private RepositoryState repositoryState; @@ -130,6 +133,11 @@ public class SolrQueryHTTPClient implements BeanFactoryAware } } + public void setAlternativeDictionary(String alternativeDictionary) + { + this.alternativeDictionary = alternativeDictionary; + } + /** * @param repositoryState the repositoryState to set */ @@ -262,7 +270,7 @@ public class SolrQueryHTTPClient implements BeanFactoryAware } url.append("&locale="); url.append(encoder.encode(locale.toString(), "UTF-8")); - + url.append("&").append(SearchParameters.ALTERNATIVE_DICTIONARY).append("=").append(alternativeDictionary); StringBuffer sortBuffer = new StringBuffer(); for (SortDefinition sortDefinition : searchParameters.getSortDefinitions()) { diff --git a/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java index 0f37a3f2e5..ad30388256 100644 --- a/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java +++ b/source/java/org/alfresco/repo/security/authentication/TicketCleanupJob.java @@ -19,7 +19,7 @@ package org.alfresco.repo.security.authentication; import org.alfresco.error.AlfrescoRuntimeException; -import org.quartz.Job; +import org.quartz.StatefulJob; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @@ -28,7 +28,7 @@ import org.quartz.JobExecutionException; * @author Andy * */ -public class TicketCleanupJob implements Job +public class TicketCleanupJob implements StatefulJob { public TicketCleanupJob() diff --git a/source/java/org/alfresco/repo/site/SiteContainersCannedQuery.java b/source/java/org/alfresco/repo/site/SiteContainersCannedQuery.java new file mode 100644 index 0000000000..f4d4ab2b90 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteContainersCannedQuery.java @@ -0,0 +1,147 @@ + +/* + * Copyright (C) 2005-2012 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.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.alfresco.query.CannedQueryParameters; +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.security.permissions.impl.acegi.AbstractCannedQueryPermissions; +import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +/** + * A canned query for fetching site containers. + * + * @author steveglover + * + */ +public class SiteContainersCannedQuery extends AbstractCannedQueryPermissions +{ + private FileFolderService fileFolderService; + private NodeService nodeService; + + public SiteContainersCannedQuery(FileFolderService fileFolderService, NodeService nodeService, CannedQueryParameters parameters, MethodSecurityBean methodSecurity) + { + super(parameters, methodSecurity); + this.fileFolderService = fileFolderService; + this.nodeService = nodeService; + } + + @Override + protected List queryAndFilter(CannedQueryParameters parameters) + { + SiteContainersCannedQueryParams paramBean = (SiteContainersCannedQueryParams)parameters.getParameterBean(); + + NodeRef siteNodeRef = paramBean.getSiteNodeRef(); + + // need to get all folders to check for site container aspect since the FileFolderService won't allow us to filter + // on aspects. Number of site containers should be relatively small. + final List containers = new ArrayList(10); + + int skip = 0; + int maxItems = 50; + List> sortProps = null; + PagingRequest pagingRequest = new PagingRequest(skip, maxItems); + PagingResults pagingResults = null; + do + { + pagingResults = fileFolderService.list(siteNodeRef, false, true, null, sortProps, pagingRequest); + + for(FileInfo folder : pagingResults.getPage()) + { + NodeRef containerNodeRef = folder.getNodeRef(); + if(nodeService.hasAspect(containerNodeRef, SiteModel.ASPECT_SITE_CONTAINER)) + { + containers.add(folder); + } + } + + if(pagingResults.hasMoreItems()) + { + skip += maxItems; + pagingRequest = new PagingRequest(skip, maxItems); + } + } while(pagingResults.hasMoreItems()); + + return containers; + } + + @Override + protected boolean isApplyPostQuerySorting() + { + return true; + } + + @SuppressWarnings({ "unchecked"}) + protected List applyPostQuerySorting(List results, CannedQuerySortDetails sortDetails) + { + @SuppressWarnings("rawtypes") + final List> sortPairs = (List)sortDetails.getSortPairs(); + if (sortPairs.size() > 0) + { + Collections.sort(results, new FileInfoComparator(sortPairs)); + } + + return results; + } + + private static class FileInfoComparator implements Comparator + { + private List> sortPairs; + + public FileInfoComparator(List> sortPairs) + { + super(); + this.sortPairs = sortPairs; + } + + @Override + public int compare(FileInfo o1, FileInfo o2) + { + int ret = 0; + + for(Pair pair : sortPairs) + { + if(pair.getFirst().equals(SiteContainersCannedQueryParams.SortFields.ContainerName)) + { + ret = o1.getName().compareTo(o2.getName()); + if(pair.getSecond().equals(SortOrder.DESCENDING)) + { + ret = ret * -1; + } + } + } + + return ret; + } + } +} diff --git a/source/java/org/alfresco/repo/site/SiteContainersCannedQueryFactory.java b/source/java/org/alfresco/repo/site/SiteContainersCannedQueryFactory.java new file mode 100644 index 0000000000..4be90decf0 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteContainersCannedQueryFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2012 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 org.alfresco.query.AbstractCannedQueryFactory; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeService; + +/** + * A canned query factory for constructing canned queries to fetch site containers. + * + * @author steveglover + * + */ +public class SiteContainersCannedQueryFactory extends AbstractCannedQueryFactory +{ + private FileFolderService fileFolderService; + private NodeService nodeService; + private MethodSecurityBean methodSecurity; + + public void setMethodSecurity(MethodSecurityBean methodSecurity) + { + this.methodSecurity = methodSecurity; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + @Override + public CannedQuery getCannedQuery(CannedQueryParameters parameters) + { + Object parameterBean = parameters.getParameterBean(); + CannedQuery cq = null; + if(parameterBean instanceof SiteContainersCannedQueryParams) + { + cq = new SiteContainersCannedQuery(fileFolderService, nodeService, parameters, methodSecurity); + } + return (CannedQuery)cq; + } + +} diff --git a/source/java/org/alfresco/repo/site/SiteContainersCannedQueryParams.java b/source/java/org/alfresco/repo/site/SiteContainersCannedQueryParams.java new file mode 100644 index 0000000000..85ab390b91 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteContainersCannedQueryParams.java @@ -0,0 +1,21 @@ +package org.alfresco.repo.site; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class SiteContainersCannedQueryParams +{ + public static enum SortFields { ContainerName }; + + private NodeRef siteNodeRef; + + public SiteContainersCannedQueryParams(NodeRef siteNodeRef) + { + super(); + this.siteNodeRef = siteNodeRef; + } + + public NodeRef getSiteNodeRef() + { + return siteNodeRef; + } +} diff --git a/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java b/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java new file mode 100644 index 0000000000..0798eb672d --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2005-2012 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.text.Collator; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.AbstractCannedQuery; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteRole; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteService.SiteMembersCallback; +import org.alfresco.util.Pair; + +/** + * A canned query for retrieving the members of a site. + * + * @author steveglover + * + */ +//TODO currently have to read all sites into memory for sorting purposes. Find a way that doesn't involve doing this. +public class SiteMembersCannedQuery extends AbstractCannedQuery +{ + private NodeService nodeService; + private PersonService personService; + private SiteService siteService; + + protected SiteMembersCannedQuery(SiteService siteService, PersonService personService, NodeService nodeService, CannedQueryParameters parameters) + { + super(parameters); + this.personService = personService; + this.nodeService = nodeService; + this.siteService = siteService; + } + + @Override + protected List queryAndFilter(CannedQueryParameters parameters) + { + SiteMembersCannedQueryParams paramBean = (SiteMembersCannedQueryParams)parameters.getParameterBean(); + + String siteShortName = paramBean.getShortName(); + boolean collapseGroups = paramBean.isCollapseGroups(); + + CannedQuerySortDetails sortDetails = parameters.getSortDetails(); + List> sortPairs = sortDetails.getSortPairs(); + + final CQSiteMembersCallback callback = new CQSiteMembersCallback(siteShortName, sortPairs); + siteService.listMembers(siteShortName, null, null, collapseGroups, callback); + callback.done(); + + return callback.getSiteMembers(); + } + + @Override + protected boolean isApplyPostQuerySorting() + { + // already sorted as a side effect by CQSiteMembersCallback + return false; + } + + private class CQSiteMembersCallback implements SiteMembersCallback + { + private String siteShortName; + private SiteInfo siteInfo; + private Set siteMembers; + + CQSiteMembersCallback(String siteShortName, List> sortPairs) + { + this.siteShortName = siteShortName; + this.siteInfo = siteService.getSite(siteShortName); + this.siteMembers = sortPairs != null && sortPairs.size() > 0 ? new TreeSet(new SiteMembershipComparator(sortPairs)) : new HashSet(); + } + + @Override + public void siteMember(String authority, String permission) + { + String firstName = null; + String lastName = null; + + if(personService.personExists(authority)) + { + NodeRef nodeRef = personService.getPerson(authority); + firstName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_FIRSTNAME); + lastName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_LASTNAME); + } + + SiteMembership siteMember = new SiteMembership(siteShortName, siteInfo.getTitle(), authority, firstName, lastName, SiteRole.valueOf(permission)); + siteMembers.add(siteMember); + } + + @Override + public boolean isDone() + { + return false; // need to read in all site members for sort + } + + List getSiteMembers() + { + // "drain" the site memberships into a (sorted) list + + List siteMemberships = new ArrayList(siteMembers.size()); + Iterator it = siteMembers.iterator(); + while(it.hasNext()) + { + siteMemberships.add(it.next()); + it.remove(); + } + return siteMemberships; + } + + void done() + { + } + } + + private static class SiteMembershipComparator implements Comparator + { + private List> sortPairs; + private static Collator collator = Collator.getInstance(); + + public SiteMembershipComparator(List> sortPairs) + { + if(sortPairs.size() < 1) + { + throw new IllegalArgumentException("Must provide at least one sort criterion"); + } + this.sortPairs = sortPairs; + } + + private int safeCompare(Comparable o1, T o2) + { + int ret = 0; + + if(o1 == null) + { + if(o2 == null) + { + ret = 0; + } + else + { + ret = -1; + } + } + else + { + if(o2 == null) + { + ret = 1; + } + else + { + ret = o1.compareTo(o2); + } + } + + return ret; + } + + private int safeCompare(String s1, String s2) + { + int ret = 0; + + if(s1 == null) + { + if(s2 == null) + { + ret = 0; + } + else + { + ret = -1; + } + } + else + { + if(s2 == null) + { + ret = 1; + } + else + { + ret = collator.compare(s1, s2); + } + } + + return ret; + } + + @Override + public int compare(SiteMembership o1, SiteMembership o2) + { + String personId1 = o1.getPersonId(); + String personId2 = o2.getPersonId(); + String shortName1 = o1.getSiteShortName(); + String shortName2 = o2.getSiteShortName(); + String firstName1 = o1.getFirstName(); + String firstName2 = o2.getFirstName(); + String lastName1 = o1.getLastName(); + String lastName2 = o2.getLastName(); + SiteRole siteRole1 = o1.getRole(); + SiteRole siteRole2 = o2.getRole(); + + int personId = safeCompare(personId1, personId2); + int firstName = safeCompare(firstName1, firstName2); + int siteShortName = safeCompare(shortName1, shortName2); + int lastName = safeCompare(lastName1, lastName2); + int siteRole = safeCompare(siteRole1, siteRole2); + + if(siteRole == 0 && siteShortName == 0 && personId == 0) + { + // equals contract + return 0; + } + + int ret = 0; + + for(Pair pair : sortPairs) + { + Object name = pair.getFirst(); + SortOrder sortOrder = pair.getSecond(); + + int multiplier = sortOrder.equals(SortOrder.ASCENDING) ? 1 : -1; + if(name.equals(SiteService.SortFields.FirstName)) + { + ret = firstName * multiplier; + } + else if(name.equals(SiteService.SortFields.LastName)) + { + if(lastName1 == null || lastName2 == null) + { + continue; + } + ret = lastName * multiplier; + } + else if(name.equals(SiteService.SortFields.Role)) + { + if(siteRole1 == null || siteRole2 == null) + { + continue; + } + ret = siteRole * multiplier; + } + + if(ret != 0) + { + break; + } + } + + return ret; + } + } +} diff --git a/source/java/org/alfresco/repo/site/SiteMembersCannedQueryParams.java b/source/java/org/alfresco/repo/site/SiteMembersCannedQueryParams.java new file mode 100644 index 0000000000..10c6f6acb3 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteMembersCannedQueryParams.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2012 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; + +/** + * Conveys parameters for the site members canned query. + * + * @author steveglover + * + */ +public class SiteMembersCannedQueryParams +{ + private String shortName; + private boolean collapseGroups; + + public SiteMembersCannedQueryParams(String shortName, boolean collapseGroups) + { + super(); + this.shortName = shortName; + this.collapseGroups = collapseGroups; + } + + public String getShortName() + { + return shortName; + } + + public boolean isCollapseGroups() + { + return collapseGroups; + } +} diff --git a/source/java/org/alfresco/repo/site/SiteMembership.java b/source/java/org/alfresco/repo/site/SiteMembership.java new file mode 100644 index 0000000000..7e4efbea32 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteMembership.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2005-2012 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 org.alfresco.service.cmr.site.SiteRole; + +/** + * Conveys information for a member of a site. + * + * @author steveglover + * + */ +public class SiteMembership +{ + private String siteShortName; + private String siteTitle; + private String personId; + private String firstName; + private String lastName; + private SiteRole role; + + public SiteMembership(String siteShortName, String siteTitle, String personId, String firstName, String lastName, SiteRole role) + { + super(); + if(siteShortName == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(personId == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(firstName == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(lastName == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(role == null) + { + throw new java.lang.IllegalArgumentException(); + } + this.siteShortName = siteShortName; + this.siteTitle = siteTitle; + this.personId = personId; + this.firstName = firstName; + this.lastName = lastName; + this.role = role; + } + + public SiteMembership(String siteShortName, String siteTitle, String personId, SiteRole role) + { + super(); + if(siteShortName == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(personId == null) + { + throw new java.lang.IllegalArgumentException(); + } + if(role == null) + { + throw new java.lang.IllegalArgumentException(); + } + + this.siteShortName = siteShortName; + this.personId = personId; + this.siteTitle = siteTitle; + this.role = role; + } + + public String getSiteShortName() + { + return siteShortName; + } + + public String getSiteTitle() + { + return siteTitle; + } + + public String getPersonId() + { + return personId; + } + + public String getFirstName() + { + return firstName; + } + + public String getLastName() + { + return lastName; + } + + public SiteRole getRole() + { + return role; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((personId == null) ? 0 : personId.hashCode()); + result = prime * result + ((role == null) ? 0 : role.hashCode()); + result = prime * result + + ((siteShortName == null) ? 0 : siteShortName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SiteMembership other = (SiteMembership) obj; + if (personId == null) { + if (other.personId != null) + return false; + } else if (!personId.equals(other.personId)) + return false; + if (role != other.role) + return false; + if (siteShortName == null) { + if (other.siteShortName != null) + return false; + } else if (!siteShortName.equals(other.siteShortName)) + return false; + return true; + } + + @Override + public String toString() + { + return "SiteMembership [siteShortName=" + siteShortName + + ", personId=" + personId + ", firstName=" + firstName + + ", lastName=" + lastName + ", role=" + role + "]"; + } + +} diff --git a/source/java/org/alfresco/repo/site/SiteMembershipCannedQueryFactory.java b/source/java/org/alfresco/repo/site/SiteMembershipCannedQueryFactory.java new file mode 100644 index 0000000000..b8b09ce7d4 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SiteMembershipCannedQueryFactory.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2012 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 org.alfresco.query.AbstractCannedQueryFactory; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteService; + +/** + * A factory for creating site membership canned queries. + * + * @author steveglover + * + */ +public class SiteMembershipCannedQueryFactory extends AbstractCannedQueryFactory +{ + private NodeService nodeService; + private PersonService personService; + private AuthorityService authorityService; + private SiteService siteService; + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + @Override + public CannedQuery getCannedQuery(CannedQueryParameters parameters) + { + Object parameterBean = parameters.getParameterBean(); + CannedQuery cq = null; + if(parameterBean instanceof SitesCannedQueryParams) + { + cq = new SitesCannedQuery(authorityService, siteService, parameters); + } + else if(parameterBean instanceof SiteMembersCannedQueryParams) + { + cq = new SiteMembersCannedQuery(siteService, personService, nodeService, parameters); + } + return (CannedQuery) cq; + } + +} diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 72900cbced..9fe9e7adf5 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -37,7 +37,11 @@ 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; @@ -68,6 +72,7 @@ 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; @@ -114,7 +119,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic { /** Logger */ private 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"); @@ -153,6 +158,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private FileFolderService fileFolderService; private SearchService searchService; private NamespaceService namespaceService; + private PreferenceService preferenceService; private PermissionService permissionService; private ActivityService activityService; private PersonService personService; @@ -169,8 +175,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private PolicyComponent policyComponent; private PublicServiceAccessService publicServiceAccessService; - private NamedObjectRegistry> cannedQueryRegistry; - + private NamedObjectRegistry> cannedQueryRegistry; /** * Set the path to the location of the sites root folder. For example: @@ -183,8 +188,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic { this.sitesXPath = sitesXPath; } + + public void setPreferenceService(PreferenceService preferenceService) + { + this.preferenceService = preferenceService; + } - /** + /** * Set node service */ public void setNodeService(NodeService nodeService) @@ -344,7 +354,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic /** * Set the registry of {@link CannedQueryFactory canned queries} */ - public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) + public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) { this.cannedQueryRegistry = cannedQueryRegistry; } @@ -391,8 +401,8 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic @Override protected void onShutdown(ApplicationEvent event) { - } - + } + /* * (non-Javadoc) * @see org.alfresco.service.cmr.site.SiteService#hasCreateSitePermissions() @@ -690,6 +700,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return getSiteGroup(shortName, true); } + /** * @see org.alfresco.service.cmr.site.SiteService#getSiteRoleGroup(java.lang.String, * java.lang.String) @@ -1000,16 +1011,19 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic { return results.getQueryExecutionId(); } + @Override public List getPage() { return siteInfos; } + @Override public boolean hasMoreItems() { return results.hasMoreItems(); } + @Override public Pair getTotalResultCount() { @@ -1017,15 +1031,15 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } }; } - - /** + + /** * This method returns the {@link SiteInfo siteInfos} for sites to which the specified user has access. * Note that if the user has access to more than 1000 sites, the list will be truncated to 1000 entries. * * @param userName the username * @return a list of {@link SiteInfo site infos}. */ - private String resolveSite(String group) + public String resolveSite(String group) { // purge non Site related Groups and strip the group name down to the site "shortName" it relates too if (group.startsWith(GROUP_SITE_PREFIX)) @@ -1112,7 +1126,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return siteInfo; } - + /** * Helper method to get the visibility of the site. If no value is present in the repository then it is calculated from the * set permissions. This will maintain backwards compatibility with earlier versions of the service implementation. @@ -1157,7 +1171,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return visibility; } - + /** * @see org.alfresco.service.cmr.site.SiteService#getSite(java.lang.String) */ @@ -1479,7 +1493,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic logger.debug("site deleted :" + shortName); } - + /** * @see org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy#onRestoreNode(org.alfresco.service.cmr.repository.ChildAssociationRef) */ @@ -1496,6 +1510,187 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic (Map>)directNodeService.getProperty(siteRef, QName.createQName(null, "memberships"))); } + public void listMembers(String shortName, final String nameFilter, final String roleFilter, final boolean collapseGroups, final SiteMembersCallback callback) + { + // MT share - for activity service system callback + if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + { + final String tenantDomain = tenantService.getDomain(shortName); + final String sName = tenantService.getBaseName(shortName, true); + + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public Void doWork() throws Exception + { + listMembersImpl(sName, nameFilter, roleFilter, collapseGroups, callback); + return null; + } + }, tenantDomain); + } + else + { + listMembersImpl(shortName, nameFilter, roleFilter, collapseGroups, callback); + } + } + + // note that this may return an authority more than once + protected void listMembersImpl(String shortName, String nameFilter, String roleFilter, boolean collapseGroups, SiteMembersCallback callback) + { + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteDoesNotExistException(shortName); + } + + // Build an array of name filter tokens pre lowercased to test against person properties + // We require that matching people have at least one match against one of these on + // either their firstname or last name + String nameFilterLower = null; + String[] nameFilters = new String[0]; + if (nameFilter != null && nameFilter.length() != 0) + { + StringTokenizer t = new StringTokenizer(nameFilter, " "); + nameFilters = new String[t.countTokens()]; + for (int i=0; t.hasMoreTokens(); i++) + { + nameFilters[i] = t.nextToken().toLowerCase(); + } + nameFilterLower = nameFilter.toLowerCase(); + } + + QName siteType = directNodeService.getType(siteNodeRef); + Set permissions = this.permissionService.getSettablePermissions(siteType); + Map groupsToExpand = new HashMap(32); + + for (String permission : permissions) + { + if (roleFilter == null || roleFilter.length() == 0 || roleFilter.equals(permission)) + { + String groupName = getSiteRoleGroup(shortName, permission, true); + Set authorities = this.authorityService.getContainedAuthorities(null, groupName, true); + for (String authority : authorities) + { + switch (AuthorityType.getAuthorityType(authority)) + { + case USER: + boolean addUser = true; + if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(authority)) + { + // found a filter - does it match person first/last name? + addUser = matchPerson(nameFilters, authority); + } + if (addUser) + { + // Add the user and their permission to the returned map + callback.siteMember(authority, permission); + } + if(callback.isDone()) + { + break; + } + break; + case GROUP: + if (collapseGroups) + { + if (!groupsToExpand.containsKey(authority)) + { + groupsToExpand.put(authority, permission); + } + } + else + { + if (nameFilter != null && nameFilter.length() != 0) + { + // found a filter - does it match Group name part? + if (authority.substring(GROUP_PREFIX_LENGTH).toLowerCase().contains(nameFilterLower)) + { + callback.siteMember(authority, permission); + } + else + { + // Does it match on the Group Display Name part instead? + String displayName = authorityService.getAuthorityDisplayName(authority); + if(displayName != null && displayName.toLowerCase().contains(nameFilterLower)) + { + callback.siteMember(authority, permission); + } + } + } + else + { + // No name filter add this group + callback.siteMember(authority, permission); + } + + if(callback.isDone()) + { + break; + } + } + break; + } + } + } + } + + if (collapseGroups) + { + for (Map.Entry entry : groupsToExpand.entrySet()) + { + Set subUsers = this.authorityService.getContainedAuthorities(AuthorityType.USER, entry.getKey(), false); + for (String subUser : subUsers) + { + boolean addUser = true; + if (nameFilter != null && nameFilter.length() != 0 && !nameFilter.equals(subUser)) + { + // found a filter - does it match person first/last name? + addUser = matchPerson(nameFilters, subUser); + } + + if (addUser) + { + // Add the collapsed user into the members list if they do not already appear in the list + callback.siteMember(subUser, entry.getValue()); + } + + if(callback.isDone()) + { + break; + } + } + } + } + } + + public PagingResults listMembersPaged(String shortName, boolean collapseGroups, List> sortProps, PagingRequest pagingRequest) + { + SiteMembershipCannedQueryFactory sitesCannedQueryFactory = (SiteMembershipCannedQueryFactory)cannedQueryRegistry.getNamedObject("sitesCannedQueryFactory"); + + CannedQueryPageDetails pageDetails = new CannedQueryPageDetails(pagingRequest.getSkipCount(), pagingRequest.getMaxItems()); + + // sort details + CannedQuerySortDetails sortDetails = null; + if(sortProps != null) + { + List> sortPairs = new ArrayList>(sortProps.size()); + for (Pair sortProp : sortProps) + { + sortPairs.add(new Pair(sortProp.getFirst(), (sortProp.getSecond() ? SortOrder.ASCENDING : SortOrder.DESCENDING))); + } + + sortDetails = new CannedQuerySortDetails(sortPairs); + } + + SiteMembersCannedQueryParams parameterBean = new SiteMembersCannedQueryParams(shortName, collapseGroups); + CannedQueryParameters params = new CannedQueryParameters(parameterBean, pageDetails, sortDetails, pagingRequest.getRequestTotalCountMax(), pagingRequest.getQueryExecutionId()); + + CannedQuery query = sitesCannedQueryFactory.getCannedQuery(params); + + CannedQueryResults results = query.execute(); + + return getPagingResults(pagingRequest, results); + } + /** * @see org.alfresco.service.cmr.site.SiteService#listMembers(java.lang.String, java.lang.String, java.lang.String, int) */ @@ -1504,9 +1699,6 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return listMembers(shortName, nameFilter, roleFilter, size, false); } - /** - * @see org.alfresco.service.cmr.site.SiteService#listMembers(String, String, String, int, boolean) - */ public Map listMembers(String shortName, final String nameFilter, final String roleFilter, final int size, final boolean collapseGroups) { // MT share - for activity service system callback @@ -1762,7 +1954,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return addUser; } - + private boolean matchByFilter(String compareString, String patternString) { if (compareString==null || compareString.isEmpty()) @@ -1929,7 +2121,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // If there are user permissions then they take priority return result.size() > 0 ? result : fullResult; } - + /** * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles() */ @@ -2044,6 +2236,40 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } } + /** + * @see org.alfresco.service.cmr.site.SiteService#canAddMember(java.lang.String, + * java.lang.String, java.lang.String) + */ + public boolean canAddMember(final String shortName, final String authorityName, final String role) + { + final NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteDoesNotExistException(shortName); + } + + // Get the user's current role + final String currentRole = getMembersRole(shortName, authorityName); + + // Get the visibility of the site + SiteVisibility visibility = getSiteVisibility(siteNodeRef); + + // If we are ... + // -- the current user has change permissions rights on the site + // or we are ... + // -- referring to a public site and + // -- the role being set is consumer and + // -- the user being added is ourselves and + // -- the member does not already have permissions + // ... then we can set the permissions as system user + final String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + return((permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) || + (SiteVisibility.PUBLIC.equals(visibility) && + role.equals(SiteModel.SITE_CONSUMER) && + authorityName.equals(currentUserName) && + currentRole == null)); + } + /** * @see org.alfresco.service.cmr.site.SiteService#setMembership(java.lang.String, * java.lang.String, java.lang.String) @@ -2066,25 +2292,8 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic { // TODO if this is the only site manager do not down grade their // permissions - - // Get the visibility of the site - SiteVisibility visibility = getSiteVisibility(siteNodeRef); - - // If we are ... - // -- the current user has change permissions rights on the site - // or we are ... - // -- referring to a public site and - // -- the role being set is consumer and - // -- the user being added is ourselves and - // -- the member does not already have permissions - // ... then we can set the permissions as system user - final String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if ((permissionService.hasPermission(siteNodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) || - (SiteVisibility.PUBLIC.equals(visibility) == true && - role.equals(SiteModel.SITE_CONSUMER) == true && - authorityName.equals(currentUserName) == true && - currentRole == null)) - { + if(canAddMember(shortName, authorityName, role)) + { // Check that we are not about to remove the last site manager checkLastManagerRemoval(shortName, authorityName, currentRole); @@ -2302,6 +2511,23 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return containerNodeRef; } + @SuppressWarnings("unchecked") + public PagingResults listContainers(String shortName, PagingRequest pagingRequest) + { + SiteContainersCannedQueryFactory sitesContainersCannedQueryFactory = (SiteContainersCannedQueryFactory)cannedQueryRegistry.getNamedObject("siteContainersCannedQueryFactory"); + + CannedQueryPageDetails pageDetails = new CannedQueryPageDetails(pagingRequest.getSkipCount(), pagingRequest.getMaxItems()); + CannedQuerySortDetails sortDetails = new CannedQuerySortDetails(new Pair(SiteContainersCannedQueryParams.SortFields.ContainerName, SortOrder.ASCENDING)); + SiteContainersCannedQueryParams parameterBean = new SiteContainersCannedQueryParams(getSiteNodeRef(shortName)); + CannedQueryParameters params = new CannedQueryParameters(parameterBean, pageDetails, sortDetails, pagingRequest.getRequestTotalCountMax(), pagingRequest.getQueryExecutionId()); + + CannedQuery query = sitesContainersCannedQueryFactory.getCannedQuery(params); + + CannedQueryResults results = query.execute(); + + return getPagingResults(pagingRequest, results); + } + /** * @see org.alfresco.service.cmr.site.SiteService#hasContainer(java.lang.String) */ @@ -2593,4 +2819,110 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } } } + + public List listSites(Set siteNames) + { + List assocs = this.nodeService.getChildrenByName( + getSiteRoot(), + ContentModel.ASSOC_CONTAINS, + siteNames); + List result = new ArrayList(assocs.size()); + for (ChildAssociationRef assoc : assocs) + { + // Ignore any node that is not a "site" type + NodeRef site = assoc.getChildRef(); + QName siteClassName = this.nodeService.getType(site); + if (dictionaryService.isSubClass(siteClassName, SiteModel.TYPE_SITE)) + { + result.add(createSiteInfo(site)); + } + } + return result; + } + + public PagingResults listSitesPaged(final String userName, List> sortProps, final PagingRequest pagingRequest) + { + SiteMembershipCannedQueryFactory sitesCannedQueryFactory = (SiteMembershipCannedQueryFactory)cannedQueryRegistry.getNamedObject("sitesCannedQueryFactory"); + + CannedQueryPageDetails pageDetails = new CannedQueryPageDetails(pagingRequest.getSkipCount(), pagingRequest.getMaxItems()); + + // sort details + CannedQuerySortDetails sortDetails = null; + if(sortProps != null) + { + List> sortPairs = new ArrayList>(sortProps.size()); + for (Pair sortProp : sortProps) + { + sortPairs.add(new Pair(sortProp.getFirst(), (sortProp.getSecond() ? SortOrder.ASCENDING : SortOrder.DESCENDING))); + } + + sortDetails = new CannedQuerySortDetails(sortPairs); + } + + SitesCannedQueryParams parameterBean = new SitesCannedQueryParams(userName); + CannedQueryParameters params = new CannedQueryParameters(parameterBean, pageDetails, sortDetails, pagingRequest.getRequestTotalCountMax(), pagingRequest.getQueryExecutionId()); + + CannedQuery query = sitesCannedQueryFactory.getCannedQuery(params); + + CannedQueryResults results = query.execute(); + + return getPagingResults(pagingRequest, results); + } + + private PagingResults getPagingResults(PagingRequest pagingRequest, final CannedQueryResults results) + { + List entities = null; + if (results.getPageCount() > 0) + { + entities = results.getPages().get(0); + } + else + { + entities = Collections.emptyList(); + } + + // set total count + final Pair totalCount; + if (pagingRequest.getRequestTotalCountMax() > 0) + { + totalCount = results.getTotalResultCount(); + } + else + { + totalCount = null; + } + + final List members = new ArrayList(entities.size()); + for (T entity : entities) + { + members.add(entity); + } + + return new PagingResults() + { + @Override + public String getQueryExecutionId() + { + return results.getQueryExecutionId(); + } + + @Override + public List getPage() + { + return members; + } + + @Override + public boolean hasMoreItems() + { + return results.hasMoreItems(); + } + + @Override + public Pair getTotalResultCount() + { + return totalCount; + } + }; + } } diff --git a/source/java/org/alfresco/repo/site/SitesCannedQuery.java b/source/java/org/alfresco/repo/site/SitesCannedQuery.java new file mode 100644 index 0000000000..5588c4d9b7 --- /dev/null +++ b/source/java/org/alfresco/repo/site/SitesCannedQuery.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2005-2012 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.text.Collator; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.query.AbstractCannedQuery; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; +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.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteRole; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.Pair; + +/** + * A canned query to retrieve the sites for a user. + * + * @author steveglover + * + */ +// TODO currently have to read all sites into memory for sorting purposes. Find a way that doesn't involve doing this. +public class SitesCannedQuery extends AbstractCannedQuery +{ + private AuthorityService authorityService; + private SiteService siteService; + + protected SitesCannedQuery(AuthorityService authorityService, SiteService siteService, CannedQueryParameters parameters) + { + super(parameters); + this.authorityService = authorityService; + this.siteService = siteService; + } + + @Override + protected List queryAndFilter(CannedQueryParameters parameters) + { + // get paramBean - note: this currently has both optional filter and optional sort param + SitesCannedQueryParams paramBean = (SitesCannedQueryParams)parameters.getParameterBean(); + + String userName = paramBean.getUsername(); + + final int size = CannedQueryPageDetails.DEFAULT_PAGE_SIZE; + + CannedQuerySortDetails sortDetails = parameters.getSortDetails(); + List> sortPairs = sortDetails.getSortPairs(); + + CQAuthorityFilter filter = new CQAuthorityFilter(userName, sortPairs); + authorityService.getContainingAuthoritiesInZone(AuthorityType.GROUP, userName, AuthorityService.ZONE_APP_SHARE, filter, size); + + return filter.getSiteMemberships(); + } + + @Override + protected boolean isApplyPostQuerySorting() + { + // already sorted as a side effect by CQAuthorityFilter + return false; + } + + private class CQAuthorityFilter implements AuthorityFilter + { + private String userName; + private Set siteMembers; + + CQAuthorityFilter(String userName, List> sortPairs) + { + this.userName = userName; + this.siteMembers = sortPairs != null && sortPairs.size() > 0 ? new TreeSet(new SiteMembershipComparator(sortPairs)) : new HashSet(); + } + + @Override + public boolean includeAuthority(String authority) + { + String siteName = siteService.resolveSite(authority); + if(siteName != null) + { + SiteInfo siteInfo = siteService.getSite(siteName); + if(siteInfo != null) + { + String role = siteService.getMembersRole(siteName, userName); + siteMembers.add(new SiteMembership(siteName, siteInfo.getTitle(), authority, SiteRole.valueOf(role))); + } + } + + return true; // need to get all items so that the canned query can do the sort in memory + } + + List getSiteMemberships() + { + // "drain" the site memberships into a (sorted) list + + List siteMemberships = new ArrayList(siteMembers.size()); + Iterator it = siteMembers.iterator(); + while(it.hasNext()) + { + siteMemberships.add(it.next()); + it.remove(); + } + return siteMemberships; + } + } + + private static class SiteMembershipComparator implements Comparator + { + private List> sortPairs; + private static Collator collator = Collator.getInstance(); + + public SiteMembershipComparator(List> sortPairs) + { + if(sortPairs.size() < 1) + { + throw new IllegalArgumentException("Must provide at least one sort criterion"); + } + this.sortPairs = sortPairs; + } + + private int safeCompare(Comparable o1, T o2) + { + int ret = 0; + + if(o1 == null) + { + if(o2 == null) + { + ret = 0; + } + else + { + ret = -1; + } + } + else + { + if(o2 == null) + { + ret = 1; + } + else + { + ret = o1.compareTo(o2); + } + } + + return ret; + } + + private int safeCompare(String s1, String s2) + { + int ret = 0; + + if(s1 == null) + { + if(s2 == null) + { + ret = 0; + } + else + { + ret = -1; + } + } + else + { + if(s2 == null) + { + ret = 1; + } + else + { + + ret = collator.compare(s1, s2); + } + } + + return ret; + } + + @Override + public int compare(SiteMembership o1, SiteMembership o2) + { + String personId1 = o1.getPersonId(); + String personId2 = o2.getPersonId(); + String shortName1 = o1.getSiteShortName(); + String shortName2 = o2.getSiteShortName(); + String firstName1 = o1.getFirstName(); + String firstName2 = o2.getFirstName(); + String lastName1 = o1.getLastName(); + String lastName2 = o2.getLastName(); + SiteRole siteRole1 = o1.getRole(); + SiteRole siteRole2 = o2.getRole(); + String siteTitle1 = o1.getSiteTitle(); + String siteTitle2 = o2.getSiteTitle(); + + int personId = safeCompare(personId1, personId2); + int firstName = safeCompare(firstName1, firstName2); + int siteShortName = safeCompare(shortName1, shortName2); + int lastName = safeCompare(lastName1, lastName2); + int siteRole = safeCompare(siteRole1, siteRole2); + int siteTitle = safeCompare(siteTitle1, siteTitle2); + + if(siteRole == 0 && siteShortName == 0 && personId == 0) + { + // equals contract + return 0; + } + + int ret = 0; + + for(Pair pair : sortPairs) + { + Object name = pair.getFirst(); + SortOrder sortOrder = pair.getSecond(); + + int multiplier = sortOrder.equals(SortOrder.ASCENDING) ? 1 : -1; + if(name.equals(SiteService.SortFields.SiteShortName)) + { + if(shortName1 == null || shortName2 == null) + { + continue; + } + ret = siteShortName * multiplier; + } + else if(name.equals(SiteService.SortFields.SiteTitle)) + { + if(siteTitle1 == null || siteTitle2 == null) + { + continue; + } + ret = siteTitle * multiplier; + } + else if(name.equals(SiteService.SortFields.Role)) + { + if(siteRole1 == null || siteRole2 == null) + { + continue; + } + ret = siteRole * multiplier; + } + + if(ret != 0) + { + break; + } + } + + return ret; + } + } +} diff --git a/source/java/org/alfresco/repo/site/SitesCannedQueryParams.java b/source/java/org/alfresco/repo/site/SitesCannedQueryParams.java new file mode 100644 index 0000000000..280d24ec9b --- /dev/null +++ b/source/java/org/alfresco/repo/site/SitesCannedQueryParams.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2012 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; + +/** + * Conveys parameters for a user site membership canned query. + * + * @author steveglover + * + */ +public class SitesCannedQueryParams +{ + private String username; + + public SitesCannedQueryParams(String username) + { + this.username = username; + } + + public String getUsername() + { + return username; + } +} diff --git a/source/java/org/alfresco/repo/tagging/InvalidTagException.java b/source/java/org/alfresco/repo/tagging/InvalidTagException.java new file mode 100644 index 0000000000..a9b1343eb9 --- /dev/null +++ b/source/java/org/alfresco/repo/tagging/InvalidTagException.java @@ -0,0 +1,16 @@ +package org.alfresco.repo.tagging; + +public class InvalidTagException extends TaggingException +{ + private static final long serialVersionUID = -7599341412701012470L; + + public InvalidTagException(String msgId) + { + super(msgId); + } + + public InvalidTagException(String msgId, Throwable cause) + { + super(msgId, cause); + } +} diff --git a/source/java/org/alfresco/repo/tagging/NonExistentTagException.java b/source/java/org/alfresco/repo/tagging/NonExistentTagException.java new file mode 100644 index 0000000000..9200c996a9 --- /dev/null +++ b/source/java/org/alfresco/repo/tagging/NonExistentTagException.java @@ -0,0 +1,17 @@ +package org.alfresco.repo.tagging; + +public class NonExistentTagException extends TaggingException +{ + private static final long serialVersionUID = 6888333159902437335L; + + public NonExistentTagException(String msgId) + { + super(msgId); + } + + public NonExistentTagException(String msgId, Throwable cause) + { + super(msgId, cause); + } + +} diff --git a/source/java/org/alfresco/repo/tagging/TagExistsException.java b/source/java/org/alfresco/repo/tagging/TagExistsException.java new file mode 100644 index 0000000000..f47b95144a --- /dev/null +++ b/source/java/org/alfresco/repo/tagging/TagExistsException.java @@ -0,0 +1,17 @@ +package org.alfresco.repo.tagging; + +public class TagExistsException extends TaggingException +{ + private static final long serialVersionUID = -1166608474107259895L; + + public TagExistsException(String msgId) + { + super(msgId); + } + + public TagExistsException(String msgId, Throwable cause) + { + super(msgId, cause); + } + +} diff --git a/source/java/org/alfresco/repo/tagging/TaggingException.java b/source/java/org/alfresco/repo/tagging/TaggingException.java new file mode 100644 index 0000000000..bc95d3ade6 --- /dev/null +++ b/source/java/org/alfresco/repo/tagging/TaggingException.java @@ -0,0 +1,18 @@ +package org.alfresco.repo.tagging; + +import org.alfresco.error.AlfrescoRuntimeException; + +public class TaggingException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 6836644764813995489L; + + public TaggingException(String msgId) + { + super(msgId); + } + + public TaggingException(String msgId, Throwable cause) + { + super(msgId, cause); + } +} diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java index 55006887bf..5185121e89 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImpl.java @@ -23,15 +23,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; +import java.text.Collator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.query.EmptyPagingResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.audit.AuditComponent; import org.alfresco.repo.copy.CopyServicePolicies; import org.alfresco.repo.copy.CopyServicePolicies.BeforeCopyPolicy; @@ -65,6 +71,7 @@ import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -87,6 +94,8 @@ public class TaggingServiceImpl implements TaggingService, protected static final String TAGGING_AUDIT_KEY_TAGS = "tags"; private static Log logger = LogFactory.getLog(TaggingServiceImpl.class); + + private static Collator collator = Collator.getInstance(); private NodeService nodeService; private NodeService nodeServiceInternal; @@ -414,7 +423,7 @@ public class TaggingServiceImpl implements TaggingService, } } - private String getTagName(NodeRef nodeRef) + public String getTagName(NodeRef nodeRef) { return (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); } @@ -454,6 +463,45 @@ public class TaggingServiceImpl implements TaggingService, } } + public NodeRef changeTag(StoreRef storeRef, String existingTag, String newTag) + { + if(existingTag == null) + { + throw new TaggingException("Existing tag cannot be null"); + } + + if(newTag == null) + { + throw new TaggingException("New tag cannot be null"); + } + + if(existingTag.equals(newTag)) + { + throw new TaggingException("New and existing tags are the same"); + } + + if(getTagNodeRef(storeRef, existingTag) == null) + { + throw new NonExistentTagException("Tag " + existingTag + " not found"); + } + + if(getTagNodeRef(storeRef, newTag) != null) + { + throw new TagExistsException("Tag " + newTag + " already exists"); + } + + List taggedNodes = findTaggedNodes(storeRef, existingTag); + for(NodeRef nodeRef : taggedNodes) + { + removeTag(nodeRef, existingTag); + addTag(nodeRef, newTag); + } + + deleteTag(storeRef, existingTag); + + return getTagNodeRef(storeRef, newTag); + } + /** * @see org.alfresco.service.cmr.tagging.TaggingService#getTags() */ @@ -513,8 +561,15 @@ public class TaggingServiceImpl implements TaggingService, * @see org.alfresco.service.cmr.tagging.TaggingService#addTag(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) */ @SuppressWarnings("unchecked") - public void addTag(final NodeRef nodeRef, final String tagName) - { + public NodeRef addTag(final NodeRef nodeRef, final String tagName) + { + NodeRef newTagNodeRef = null; + + if(tagName == null) + { + throw new IllegalArgumentException("Must provide a non-null tag"); + } + updateTagBehaviour.disable(); createTagBehaviour.disable(); try @@ -523,7 +578,7 @@ public class TaggingServiceImpl implements TaggingService, String tag = tagName.toLowerCase(); // Get the tag node reference - NodeRef newTagNodeRef = getTagNodeRef(nodeRef.getStoreRef(), tag, true); + newTagNodeRef = getTagNodeRef(nodeRef.getStoreRef(), tag, true); List tagNodeRefs = new ArrayList(5); if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TAGGABLE) == false) @@ -554,17 +609,22 @@ public class TaggingServiceImpl implements TaggingService, updateTagBehaviour.enable(); createTagBehaviour.enable(); } + + return newTagNodeRef; } /** * @see org.alfresco.service.cmr.tagging.TaggingService#addTags(org.alfresco.service.cmr.repository.NodeRef, java.util.List) */ - public void addTags(NodeRef nodeRef, List tags) + public List> addTags(NodeRef nodeRef, List tags) { + List> ret = new ArrayList>(); for (String tag : tags) { - addTag(nodeRef, tag); + NodeRef tagNodeRef = addTag(nodeRef, tag); + ret.add(new Pair(tag, tagNodeRef)); } + return ret; } /** @@ -653,6 +713,140 @@ public class TaggingServiceImpl implements TaggingService, } } + /** + * @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.query.PagingRequest) + */ + @SuppressWarnings("unchecked") + // TODO canned query + public PagingResults> getTags(NodeRef nodeRef, PagingRequest pagingRequest) + { + // Check for the taggable aspect + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TAGGABLE) == true) + { + // Get the current tags + List currentTagNodes = (List)this.nodeService.getProperty(nodeRef, ContentModel.PROP_TAGS); + if (currentTagNodes != null) + { + final int totalItems = currentTagNodes.size(); + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + int end = maxItems == Integer.MAX_VALUE ? totalItems : skipCount + maxItems; + int size = (maxItems == Integer.MAX_VALUE ? totalItems : maxItems); + + final List> sortedTags = new ArrayList>(size); + // grab all tags and sort (assume fairly low number of tags) + for(NodeRef tagNode : currentTagNodes) + { + String tag = (String)this.nodeService.getProperty(tagNode, ContentModel.PROP_NAME); + sortedTags.add(new Pair(tagNode, tag)); + } + Collections.sort(sortedTags, new Comparator>() + { + @Override + public int compare(Pair o1, Pair o2) + { + String tag1 = o1.getSecond(); + String tag2 = o2.getSecond(); + return collator.compare(tag1, tag2); + } + }); + + final List> result = new ArrayList>(size); + Iterator> it = sortedTags.iterator(); + for(int count = 0; count < end && it.hasNext(); count++) + { + Pair tagPair = it.next(); + + if(count < skipCount) + { + continue; + } + + result.add(tagPair); + } + currentTagNodes = null; + final boolean hasMoreItems = end < totalItems; + + return new PagingResults>() + { + @Override + public List> getPage() + { + return result; + } + + @Override + public boolean hasMoreItems() + { + return hasMoreItems; + } + + @Override + public Pair getTotalResultCount() + { + Integer total = Integer.valueOf(totalItems); + return new Pair(total, total); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } + } + + return new EmptyPagingResults>(); + } + + /** + * @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.StoreRef, org.alfresco.query.PagingRequest) + */ + public PagingResults> getTags(StoreRef storeRef, PagingRequest pagingRequest) + { + ParameterCheck.mandatory("storeRef", storeRef); + + PagingResults rootCategories = this.categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, pagingRequest, true); + final List> result = new ArrayList>(rootCategories.getPage().size()); + for (ChildAssociationRef rootCategory : rootCategories.getPage()) + { + String name = (String)this.nodeService.getProperty(rootCategory.getChildRef(), ContentModel.PROP_NAME); + result.add(new Pair(rootCategory.getChildRef(), name)); + } + final boolean hasMoreItems = rootCategories.hasMoreItems(); + final Pair totalResultCount = rootCategories.getTotalResultCount(); + final String queryExecutionId = rootCategories.getQueryExecutionId(); + rootCategories = null; + + return new PagingResults>() + { + @Override + public List> getPage() + { + return result; + } + + @Override + public boolean hasMoreItems() + { + return hasMoreItems; + } + + @Override + public Pair getTotalResultCount() + { + return totalResultCount; + } + + @Override + public String getQueryExecutionId() + { + return queryExecutionId; + } + }; + } + /** * @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.NodeRef) */ diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java index 5dd7959e11..3068074bc5 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java @@ -49,6 +49,8 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; import org.alfresco.service.cmr.thumbnail.ThumbnailException; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; @@ -90,12 +92,16 @@ public class ThumbnailServiceImpl implements ThumbnailService, /** Behaviour filter */ private BehaviourFilter behaviourFilter; + /** Rule service */ + private RuleService ruleService; + /** * The policy component. * @since 3.5.0 */ private PolicyComponent policyComponent; + /** * Set the behaviour filter. * @@ -145,6 +151,14 @@ public class ThumbnailServiceImpl implements ThumbnailService, this.policyComponent = policyComponent; } + /** + * @param ruleService rule service + */ + public void setRuleService(RuleService ruleService) + { + this.ruleService = ruleService; + } + /** * Registers to listen for events of interest. * @since 3.5.0 @@ -692,7 +706,16 @@ public class ThumbnailServiceImpl implements ThumbnailService, thumbnailMods.add(lastModifiedValue); // Set the property... - nodeService.setProperty(parentNode, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods); + + ruleService.disableRuleType(RuleType.UPDATE); + try + { + nodeService.setProperty(parentNode, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods); + } + finally + { + ruleService.enableRuleType(RuleType.UPDATE); + } } else { @@ -703,7 +726,16 @@ public class ThumbnailServiceImpl implements ThumbnailService, // Add the aspect with the new property... Map properties = new HashMap(); properties.put(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods); - nodeService.addAspect(parentNode, ContentModel.ASPECT_THUMBNAIL_MODIFICATION, properties); + + ruleService.disableRuleType(RuleType.UPDATE); + try + { + nodeService.addAspect(parentNode, ContentModel.ASPECT_THUMBNAIL_MODIFICATION, properties); + } + finally + { + ruleService.enableRuleType(RuleType.UPDATE); + } } } } diff --git a/source/java/org/alfresco/service/cmr/activities/ActivityService.java b/source/java/org/alfresco/service/cmr/activities/ActivityService.java index dcbfb0d8bf..e9ac474988 100644 --- a/source/java/org/alfresco/service/cmr/activities/ActivityService.java +++ b/source/java/org/alfresco/service/cmr/activities/ActivityService.java @@ -21,6 +21,8 @@ package org.alfresco.service.cmr.activities; import java.util.List; import java.util.Set; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.service.NotAuditable; @@ -140,6 +142,9 @@ public interface ActivityService extends ActivityPostService @NotAuditable public List getUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, Set userFilter, Set actvityFilter, long minFeedId); + @NotAuditable + public PagingResults getPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest); + /** * Retrieve site feed * diff --git a/source/java/org/alfresco/service/cmr/preference/PreferenceService.java b/source/java/org/alfresco/service/cmr/preference/PreferenceService.java index 1cd858fee6..61178ca916 100644 --- a/source/java/org/alfresco/service/cmr/preference/PreferenceService.java +++ b/source/java/org/alfresco/service/cmr/preference/PreferenceService.java @@ -21,7 +21,11 @@ package org.alfresco.service.cmr.preference; import java.io.Serializable; import java.util.Map; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.service.Auditable; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.util.Pair; /** * @author Roy Wetherall @@ -37,6 +41,9 @@ public interface PreferenceService @Auditable(parameters = {"userName"}) Map getPreferences(String userName); + @Auditable(parameters = {"userName", "preferenceName"}) + Serializable getPreference(String userName, String preferenceName); + /** * Get the preferences for a particular user. *

@@ -52,6 +59,9 @@ public interface PreferenceService @Auditable(parameters = {"userName", "preferenceFilter"}) Map getPreferences(String userName, String preferenceFilter); + @Auditable(parameters = {"userName", "preferenceFilter"}) + PagingResults> getPagedPreferences(String userName, String preferenceFilter, PagingRequest pagingRequest); + /** * Sets the preference values for a user. *

@@ -84,5 +94,40 @@ public interface PreferenceService */ @Auditable(parameters = {"userName", "preferenceFilter"}) void clearPreferences(String userName, String preferenceFilter); + + /** + * Is siteShortName a favourite site of username? + * + * @param userName the user name + * @param siteShortName the site short name + */ + @Auditable(parameters = {"userName", "siteShortName"}) + boolean isFavouriteSite(String userName, String siteShortName); + /** + * Returns a paged list of favourite sites for the user. + * + * @param userName the user name + * @param pagingRequest paging request + */ + @Auditable(parameters = {"userName", "pagingRequest"}) + PagingResults getFavouriteSites(String userName, PagingRequest pagingRequest); + + /** + * Adds siteShortName as a favourite site for the user. + * + * @param userName the user name + * @param siteShortName the site short name + */ + @Auditable(parameters = {"userName", "siteShortName"}) + void addFavouriteSite(String userName, String siteShortName); + + /** + * Removes siteShortName as a favourite site for the user. + * + * @param userName the user name + * @param siteShortName the site short name + */ + @Auditable(parameters = {"userName", "siteShortName"}) + void removeFavouriteSite(String userName, String siteShortName); } diff --git a/source/java/org/alfresco/service/cmr/search/CategoryService.java b/source/java/org/alfresco/service/cmr/search/CategoryService.java index 9814e4f355..2dc5ee3000 100644 --- a/source/java/org/alfresco/service/cmr/search/CategoryService.java +++ b/source/java/org/alfresco/service/cmr/search/CategoryService.java @@ -21,8 +21,9 @@ package org.alfresco.service.cmr.search; import java.util.Collection; import java.util.List; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.service.Auditable; -import org.alfresco.service.PublicService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -101,6 +102,16 @@ public interface CategoryService @Auditable(parameters = {"storeRef", "aspectName"}) public Collection getRootCategories(StoreRef storeRef, QName aspectName); + /** + * Get a paged list of the root categories for an aspect/classification + * + * @param storeRef + * @param aspectName + * @return + */ + @Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName"}) + PagingResults getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName); + /** * Looks up a category by name under its immediate parent. Index-independent so can be used for cluster-safe * existence checks. diff --git a/source/java/org/alfresco/service/cmr/site/SiteRole.java b/source/java/org/alfresco/service/cmr/site/SiteRole.java new file mode 100644 index 0000000000..9c8bdb8e23 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/site/SiteRole.java @@ -0,0 +1,7 @@ +package org.alfresco.service.cmr.site; + +public enum SiteRole +{ + // note: lexical ordering + SiteCollaborator, SiteConsumer, SiteContributor, SiteManager; +} diff --git a/source/java/org/alfresco/service/cmr/site/SiteService.java b/source/java/org/alfresco/service/cmr/site/SiteService.java index 8761ec0f19..2226d05295 100644 --- a/source/java/org/alfresco/service/cmr/site/SiteService.java +++ b/source/java/org/alfresco/service/cmr/site/SiteService.java @@ -27,8 +27,10 @@ import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.node.getchildren.FilterProp; import org.alfresco.repo.security.authority.UnknownAuthorityException; +import org.alfresco.repo.site.SiteMembership; import org.alfresco.service.Auditable; import org.alfresco.service.NotAuditable; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; @@ -44,6 +46,22 @@ public interface SiteService { static String DOCUMENT_LIBRARY = "documentLibrary"; + public enum SortFields { LastName, FirstName, Role, SiteShortName, SiteTitle }; + + public interface SiteMembersCallback + { + /* + * A site member along with his/her Site permission + */ + public void siteMember(String authority, String permission); + + /** + * Return true to break out of the loop early. + * @return + */ + public boolean isDone(); + } + /** * Create a new site. * @@ -58,6 +76,18 @@ public interface SiteService @Auditable(parameters = {"sitePreset", "shortName"}) SiteInfo createSite(String sitePreset, String shortName, String title, String description, boolean isPublic); + /** + * Can the current user add the authority "authorityName" to the site "shortName" with role "role"? + * + * @param shortName site short name, must be unique + * @param authorityName authority to add + * @param role site role + * + * @return true if the current user can add the authority to the site, false otherwise + */ + @NotAuditable + boolean canAddMember(String shortName, String authorityName, String role); + /** * Create a new site. * @@ -167,6 +197,7 @@ public interface SiteService * @return a page of SiteInfo objects. * @since 4.0 */ + @NotAuditable PagingResults listSites(List filterProps, List> sortProps, PagingRequest pagingRequest); /** @@ -217,6 +248,20 @@ public interface SiteService */ @Auditable(parameters = {"shortName"}) void deleteSite(String shortName); + + /** + * List the members of the site. This includes both users and groups. + *

+ * Name and role filters are optional and if not specified all the members of the site are returned. + * + * @param shortName site short name + * @param nameFilter name filter + * @param roleFilter role filter + * @param collapseGroups true if collapse member groups into user list, false otherwise + * @param callback callback + */ + @NotAuditable + void listMembers(String shortName, final String nameFilter, final String roleFilter, boolean collapseGroups, SiteMembersCallback callback); /** * List the members of the site. This includes both users and groups. @@ -263,14 +308,18 @@ public interface SiteService /** * Gets the role of the specified user. + * Returns a paged list of the members of the site. This includes both users and groups if collapseGroups is set to false, otherwise all + * groups that are members are collapsed into their component users and listed. * - * @param shortName site short name - * @param authorityName full authority name (so if it's a group then its prefixed with 'GROUP_') - * @return String site role, null if none + * @param shortName site short name + * @param collapseGroups true if collapse member groups into user list, false otherwise + * @param pagingRequest the paging request + * + * @return Map the authority name and their role */ @NotAuditable - String getMembersRole(String shortName, String authorityName); - + PagingResults listMembersPaged(String shortName, boolean collapseGroups, List> sortProps, PagingRequest pagingRequest); + /** * Gets the extended role information of the specified user. * @@ -340,6 +389,17 @@ public interface SiteService @NotAuditable NodeRef getContainer(String shortName, String componentId); + /** + * Returns a paged list of top level containers for the site + * + * @param shortName short name of site + * @param pagingRequest paging request + + * @return paged list of top level containers + */ + @NotAuditable + PagingResults listContainers(String shortName, PagingRequest pagingRequest); + /** * Determines if a "container" folder for the specified component exists. * @@ -407,4 +467,19 @@ public interface SiteService * @since 3.4.2 */ public void cleanSitePermissions(NodeRef relocatedNode, SiteInfo containingSite); + + /** + * List all the sites that the specified user has a explicit membership to. + * + * @param userName user name + * @return PagingResults paged list of site information + */ + @NotAuditable + PagingResults listSitesPaged(final String userName, List> sortProps, final PagingRequest pagingRequest); + + @NotAuditable + String resolveSite(String group); + + @NotAuditable + String getMembersRole(String shortName, String authorityName); } diff --git a/source/java/org/alfresco/service/cmr/tagging/TaggingService.java b/source/java/org/alfresco/service/cmr/tagging/TaggingService.java index 0c4da6326f..af65954143 100644 --- a/source/java/org/alfresco/service/cmr/tagging/TaggingService.java +++ b/source/java/org/alfresco/service/cmr/tagging/TaggingService.java @@ -20,11 +20,13 @@ package org.alfresco.service.cmr.tagging; import java.util.List; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; import org.alfresco.service.Auditable; import org.alfresco.service.NotAuditable; -import org.alfresco.service.PublicService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; /** * Tagging Service Interface @@ -43,6 +45,8 @@ public interface TaggingService @NotAuditable boolean isTag(StoreRef storeRef, String tag); + public String getTagName(NodeRef nodeRef); + /** * Get all the tags currently available * @@ -51,6 +55,16 @@ public interface TaggingService @NotAuditable List getTags(StoreRef storeRef); + /** + * Get a paged list of all the tags currently available + * + * @param storeRef + * @param pagingRequest + * @return + */ + @NotAuditable + PagingResults> getTags(StoreRef storeRef, PagingRequest pagingRequest); + /** * Get all the tags currently available that match the provided filter. * @@ -79,6 +93,9 @@ public interface TaggingService @Auditable(parameters = {"tag"}) void deleteTag(StoreRef storeRef, String tag); + @Auditable(parameters = {"existingTag", "newTag"}) + NodeRef changeTag(StoreRef storeRef, String existingTag, String newTag); + /** * Indicates whether a node has the specified tag or not. * @@ -96,7 +113,7 @@ public interface TaggingService * @param tag tag name */ @Auditable(parameters = {"tag"}) - void addTag(NodeRef nodeRef, String tag); + NodeRef addTag(NodeRef nodeRef, String tag); /** * Gets the node reference for a given tag. @@ -119,7 +136,7 @@ public interface TaggingService * @param tags list of tags */ @Auditable(parameters = {"tags"}) - void addTags(NodeRef nodeRef, List tags); + List> addTags(NodeRef nodeRef, List tags); /** * Remove a tag from a node. @@ -148,6 +165,15 @@ public interface TaggingService @NotAuditable List getTags(NodeRef nodeRef); + /** + * Get a paged list of all the tags on a node + * + * @param nodeRef node reference + * @return PagingResults list of tags on the node + */ + @NotAuditable + PagingResults> getTags(NodeRef nodeRef, PagingRequest pagingRequest); + /** * Sets the list of tags that are applied to a node, replaces any existing * tags with those provided. diff --git a/source/java/org/alfresco/service/cmr/view/ImporterBinding.java b/source/java/org/alfresco/service/cmr/view/ImporterBinding.java index 05ff6794f8..2102e7cc56 100644 --- a/source/java/org/alfresco/service/cmr/view/ImporterBinding.java +++ b/source/java/org/alfresco/service/cmr/view/ImporterBinding.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -66,5 +66,6 @@ public interface ImporterBinding * @return list of model class qnames to exclude (return null to indicate use of default list) */ public QName[] getExcludedClasses(); - + + public ImporterContentCache getImportConentCache(); } diff --git a/source/java/org/alfresco/service/cmr/view/ImporterContentCache.java b/source/java/org/alfresco/service/cmr/view/ImporterContentCache.java new file mode 100644 index 0000000000..8683219441 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/view/ImporterContentCache.java @@ -0,0 +1,26 @@ +/* + * 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.service.cmr.view; + +import org.alfresco.service.cmr.repository.ContentData; + +public interface ImporterContentCache +{ + public ContentData getContent(ImportPackageHandler handler, ContentData sourceContent); +} diff --git a/source/java/org/alfresco/tools/Import.java b/source/java/org/alfresco/tools/Import.java index 8f3544f236..538330c308 100644 --- a/source/java/org/alfresco/tools/Import.java +++ b/source/java/org/alfresco/tools/Import.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -29,6 +29,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.view.ImportPackageHandler; import org.alfresco.service.cmr.view.ImporterBinding; +import org.alfresco.service.cmr.view.ImporterContentCache; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.cmr.view.ImporterProgress; import org.alfresco.service.cmr.view.ImporterService; @@ -514,37 +515,35 @@ public class Import extends Tool { this.uuidBinding = uuidBinding; } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getUUIDBinding() - */ + + @Override public UUID_BINDING getUUIDBinding() { return uuidBinding; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#allowReferenceWithinTransaction() - */ + @Override public boolean allowReferenceWithinTransaction() { return false; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.view.ImporterBinding#getValue(java.lang.String) - */ + @Override public String getValue(String key) { return null; } + @Override public QName[] getExcludedClasses() { return new QName[] {}; } + + @Override + public ImporterContentCache getImportConentCache() + { + return null; + } } } diff --git a/source/java/org/alfresco/util/TypeConstraint.java b/source/java/org/alfresco/util/TypeConstraint.java new file mode 100644 index 0000000000..e1e71c6891 --- /dev/null +++ b/source/java/org/alfresco/util/TypeConstraint.java @@ -0,0 +1,203 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Stores a set of expected and excluded types, by full type name. For excluded types, the localName can be a wildcard (*) to indicate that the + * whole namespace should be excluded. + * + * A node is tested to ensure that its type is in the expected list and not in the excluded list. Its aspects are also + * tested to ensure that they are in the expected list and not in the excluded list. + * + * Adapted some code from QNameFilter. + * + * @author steveglover + * + */ +public class TypeConstraint +{ + public static final String WILDCARD = "*"; + + private List expectedTypes; + private Set excludedQNames; + private Set excludedModels; + private List excludedTypes; + private NodeService nodeService; + + public void setExpectedTypes(List expectedTypes) + { + if(expectedTypes != null && expectedTypes.size() > 0) + { + this.expectedTypes = new ArrayList(expectedTypes.size()); + + for(String type : expectedTypes) + { + final QName typeDef = QName.createQName(type); + this.expectedTypes.add(typeDef); + } + } + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setExcludedTypes(List excludedTypes) + { + this.excludedTypes = excludedTypes; + } + + public void init() + { + if(excludedTypes != null && !excludedTypes.isEmpty()) + { + preprocessExcludedTypes(excludedTypes); + } + } + + /** + * Processes the user-defined list of types into valid QNames & models, it validates them + * against the dictionary and also supports wildcards + * @param excludeTypeNames + * @return Set Valid type QNames + */ + protected void preprocessExcludedTypes(List excludeTypeNames) + { + if (excludeTypeNames == null || excludeTypeNames.isEmpty()) return; + + Set qNamesToExclude = new HashSet(excludeTypeNames.size()); + Set modelsToExclude = new HashSet(); + + for (String typeDefinition : excludeTypeNames) + { + final QName typeDef = QName.createQName(typeDefinition); + if (WILDCARD.equals(typeDef.getLocalName())) + { + modelsToExclude.add(typeDef.getNamespaceURI()); + } + else + { + qNamesToExclude.add(typeDef); // valid so add it to the list + } + } + + this.excludedModels = modelsToExclude; + this.excludedQNames = qNamesToExclude; + } + + private boolean isExcluded(QName typeQName) + { + return excludedQNames != null && excludedQNames.contains(typeQName) || excludedModels != null && excludedModels.contains(typeQName.getNamespaceURI()); + } + + private boolean matchesExpected(QName typeQName) + { + return expectedTypes == null || expectedTypes.contains(typeQName); + } + + /** + * Returns true if the nodeRef matches the constraints, false otherwise. + * + * @param nodeRef + * @return returns true if the nodeRef matches the constraints, false otherwise. + */ + public boolean matches(final NodeRef nodeRef) + { + // need to run as system - caller may not be able to read the node's aspects + // but we need to know what they are in order to determine exclusion. + boolean matches = TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + @Override + public Boolean doWork() throws Exception + { + boolean matches = true; + + QName nodeType = nodeService.getType(nodeRef); + boolean typeExcluded = isExcluded(nodeType); + + if(typeExcluded) + { + // if the type is excluded, bail out + matches = false; + } + else + { + boolean isExpected = matchesExpected(nodeType); + if(isExpected) + { + // type is expected and not excluded, check the aspects for exclusion + Set aspects = nodeService.getAspects(nodeRef); + for(QName aspect : aspects) + { + if(isExcluded(aspect)) + { + matches = false; + break; + } + } + } + else + { + // type not in expected list, check aspects for exclusion and in the expected list + + // check aspects + Set aspects = nodeService.getAspects(nodeRef); + for(QName aspect : aspects) + { + if(isExcluded(aspect)) + { + // aspect is excluded, bail out + matches = false; + break; + } + + if(matchesExpected(aspect)) + { + // aspect matches an expected type + isExpected = true; + } + } + + if(!isExpected) + { + // neither the type nor any of the aspects in the expected list, no match + matches = false; + } + } + } + + return matches; + } + }, TenantUtil.getCurrentDomain()); + + return matches; + } +}