mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
98423: Merged 5.0.N (5.0.2) to HEAD-BUG-FIX (5.1/Cloud) 98310: Merged V4.2-BUG-FIX (4.2.5) to 5.0.N (5.0.2) 98244: Merged V4.2.3 (4.2.3.18) to V4.2-BUG-FIX (4.2.5) 97968: Merged DEV to PATCHES/V4.2.3 (4.2.3.18) 97890: MNT-13349: OpenCMIS bulkUpdateProperties method fails when run against more than one document - Implemented Batch Processing for bulk update properties. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@98530 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
4050 lines
144 KiB
Java
4050 lines
144 KiB
Java
/*
|
|
* Copyright (C) 2005-2014 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.opencmis;
|
|
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.SequenceInputStream;
|
|
import java.io.Serializable;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.EnumSet;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Set;
|
|
import java.util.TimeZone;
|
|
import java.util.TreeSet;
|
|
|
|
import javax.xml.datatype.DatatypeConfigurationException;
|
|
import javax.xml.datatype.DatatypeFactory;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.events.types.ContentEvent;
|
|
import org.alfresco.events.types.ContentEventImpl;
|
|
import org.alfresco.events.types.ContentReadRangeEvent;
|
|
import org.alfresco.events.types.Event;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo;
|
|
import org.alfresco.opencmis.dictionary.CMISActionEvaluator;
|
|
import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum;
|
|
import org.alfresco.opencmis.dictionary.CMISDictionaryService;
|
|
import org.alfresco.opencmis.dictionary.CMISNodeInfo;
|
|
import org.alfresco.opencmis.dictionary.CMISObjectVariant;
|
|
import org.alfresco.opencmis.dictionary.CMISPropertyAccessor;
|
|
import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper;
|
|
import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper;
|
|
import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper;
|
|
import org.alfresco.opencmis.mapping.DirectProperty;
|
|
import org.alfresco.opencmis.search.CMISQueryOptions;
|
|
import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode;
|
|
import org.alfresco.opencmis.search.CMISQueryService;
|
|
import org.alfresco.opencmis.search.CMISResultSet;
|
|
import org.alfresco.opencmis.search.CMISResultSetColumn;
|
|
import org.alfresco.opencmis.search.CMISResultSetRow;
|
|
import org.alfresco.repo.Client;
|
|
import org.alfresco.repo.Client.ClientType;
|
|
import org.alfresco.repo.action.executer.ContentMetadataExtracter;
|
|
import org.alfresco.repo.cache.SimpleCache;
|
|
import org.alfresco.repo.events.EventPreparator;
|
|
import org.alfresco.repo.events.EventPublisher;
|
|
import org.alfresco.repo.model.filefolder.GetChildrenCannedQuery;
|
|
import org.alfresco.repo.model.filefolder.HiddenAspect;
|
|
import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility;
|
|
import org.alfresco.repo.policy.BehaviourFilter;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
|
import org.alfresco.repo.security.permissions.PermissionReference;
|
|
import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl;
|
|
import org.alfresco.repo.security.permissions.impl.ModelDAO;
|
|
import org.alfresco.repo.tenant.TenantAdminService;
|
|
import org.alfresco.repo.tenant.TenantDeployer;
|
|
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
|
|
import org.alfresco.repo.thumbnail.ThumbnailHelper;
|
|
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.repo.version.VersionBaseModel;
|
|
import org.alfresco.repo.version.VersionModel;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.action.ActionService;
|
|
import org.alfresco.service.cmr.audit.AuditQueryParameters;
|
|
import org.alfresco.service.cmr.audit.AuditService;
|
|
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
|
|
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
|
|
import org.alfresco.service.cmr.dictionary.AspectDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.dictionary.InvalidAspectException;
|
|
import org.alfresco.service.cmr.lock.LockService;
|
|
import org.alfresco.service.cmr.model.FileExistsException;
|
|
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.rendition.RenditionService;
|
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
|
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
|
import org.alfresco.service.cmr.repository.MimetypeService;
|
|
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.cmr.repository.Path.ChildAssocElement;
|
|
import org.alfresco.service.cmr.repository.StoreRef;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AccessPermission;
|
|
import org.alfresco.service.cmr.security.AccessStatus;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.cmr.security.PermissionService;
|
|
import org.alfresco.service.cmr.site.SiteInfo;
|
|
import org.alfresco.service.cmr.site.SiteService;
|
|
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
|
|
import org.alfresco.service.cmr.version.VersionHistory;
|
|
import org.alfresco.service.cmr.version.VersionService;
|
|
import org.alfresco.service.cmr.version.VersionType;
|
|
import org.alfresco.service.descriptor.Descriptor;
|
|
import org.alfresco.service.descriptor.DescriptorService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.FileFilterMode;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.TempFileProvider;
|
|
import org.apache.chemistry.opencmis.commons.BasicPermissions;
|
|
import org.apache.chemistry.opencmis.commons.PropertyIds;
|
|
import org.apache.chemistry.opencmis.commons.data.Ace;
|
|
import org.apache.chemistry.opencmis.commons.data.Acl;
|
|
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
|
|
import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
|
|
import org.apache.chemistry.opencmis.commons.data.ContentStream;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectData;
|
|
import org.apache.chemistry.opencmis.commons.data.ObjectList;
|
|
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
|
|
import org.apache.chemistry.opencmis.commons.data.Properties;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyData;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyId;
|
|
import org.apache.chemistry.opencmis.commons.data.PropertyString;
|
|
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.PermissionDefinition;
|
|
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
|
|
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
|
|
import org.apache.chemistry.opencmis.commons.enums.Action;
|
|
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
|
|
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
|
|
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
|
|
import org.apache.chemistry.opencmis.commons.enums.ChangeType;
|
|
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
|
|
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
|
|
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
|
|
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
|
|
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
|
|
import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
|
|
import org.apache.chemistry.opencmis.commons.enums.Updatability;
|
|
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
|
|
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
|
|
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.CmisStreamNotSupportedException;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
|
|
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
|
|
import org.apache.chemistry.opencmis.commons.server.CallContext;
|
|
import org.apache.chemistry.opencmis.commons.server.CmisService;
|
|
import org.apache.chemistry.opencmis.commons.spi.Holder;
|
|
import org.apache.commons.io.IOUtils;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.beans.BeansException;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.ApplicationContextAware;
|
|
import org.springframework.context.ApplicationEvent;
|
|
import org.springframework.context.ApplicationListener;
|
|
import org.springframework.context.event.ApplicationContextEvent;
|
|
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
/**
|
|
* Bridge connecting Alfresco and OpenCMIS.
|
|
* <p/>
|
|
* This class provides many of the typical services that the {@link CmisService} implementation
|
|
* will need to use Alfresco.
|
|
*
|
|
* @author florian.mueller
|
|
* @author Derek Hulley
|
|
* @author steveglover
|
|
*/
|
|
public class CMISConnector implements ApplicationContextAware, ApplicationListener<ApplicationContextEvent>, TenantDeployer
|
|
{
|
|
private static Log logger = LogFactory.getLog(CMISConnector.class);
|
|
|
|
// mappings from cmis property names to their Alfresco property name counterparts (used by getChildren)
|
|
private static Map<String, QName> SORT_PROPERTY_MAPPINGS = new HashMap<String, QName>();
|
|
|
|
static
|
|
{
|
|
SORT_PROPERTY_MAPPINGS.put(PropertyIds.LAST_MODIFICATION_DATE, ContentModel.PROP_MODIFIED);
|
|
SORT_PROPERTY_MAPPINGS.put(PropertyIds.CREATION_DATE, ContentModel.PROP_CREATED);
|
|
}
|
|
|
|
public static final char ID_SEPERATOR = ';';
|
|
public static final String ASSOC_ID_PREFIX = "assoc:";
|
|
public static final String PWC_VERSION_LABEL = "pwc";
|
|
public static final String UNVERSIONED_VERSION_LABEL = "1.0";
|
|
|
|
public static final String RENDITION_NONE = "cmis:none";
|
|
|
|
public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog";
|
|
|
|
public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org";
|
|
public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/";
|
|
public static final String ASPECTS = "aspects";
|
|
public static final String SET_ASPECTS = "setAspects";
|
|
public static final String APPLIED_ASPECTS = "appliedAspects";
|
|
public static final String ASPECTS_TO_ADD = "aspectsToAdd";
|
|
public static final String ASPECTS_TO_REMOVE = "aspectsToRemove";
|
|
public static final String PROPERTIES = "properties";
|
|
|
|
private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50);
|
|
private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1);
|
|
private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200);
|
|
private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10);
|
|
|
|
private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId";
|
|
private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId";
|
|
private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId";
|
|
|
|
private static final String CMIS_USER = "cmis:user";
|
|
|
|
private static final BigInteger maxInt = BigInteger.valueOf(Integer.MAX_VALUE);
|
|
private static final BigInteger minInt = BigInteger.valueOf(Integer.MIN_VALUE);
|
|
private static final BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
|
private static final BigInteger minLong = BigInteger.valueOf(Long.MIN_VALUE);
|
|
|
|
// lifecycle
|
|
private ProcessorLifecycle lifecycle = new ProcessorLifecycle();
|
|
|
|
// Alfresco objects
|
|
private DescriptorService descriptorService;
|
|
private NodeService nodeService;
|
|
private VersionService versionService;
|
|
private CheckOutCheckInService checkOutCheckInService;
|
|
private LockService lockService;
|
|
private ContentService contentService;
|
|
private RenditionService renditionService;
|
|
private FileFolderService fileFolderService;
|
|
private TenantAdminService tenantAdminService;
|
|
private TransactionService transactionService;
|
|
private AuthenticationService authenticationService;
|
|
private PermissionService permissionService;
|
|
private ModelDAO permissionModelDao;
|
|
private CMISDictionaryService cmisDictionaryService;
|
|
private CMISDictionaryService cmisDictionaryService11;
|
|
private CMISQueryService cmisQueryService;
|
|
private CMISQueryService cmisQueryService11;
|
|
private MimetypeService mimetypeService;
|
|
private AuditService auditService;
|
|
private NamespaceService namespaceService;
|
|
private SearchService searchService;
|
|
private DictionaryService dictionaryService;
|
|
private SiteService siteService;
|
|
private ActionService actionService;
|
|
private ThumbnailService thumbnailService;
|
|
private ServiceRegistry serviceRegistry;
|
|
private EventPublisher eventPublisher;
|
|
|
|
private CmisActivityPoster activityPoster;
|
|
|
|
private BehaviourFilter behaviourFilter;
|
|
|
|
private HiddenAspect hiddenAspect;
|
|
|
|
private StoreRef storeRef;
|
|
private String rootPath;
|
|
private Map<String, List<String>> kindToRenditionNames;
|
|
|
|
// note: cache is tenant-aware (if using TransctionalCache impl)
|
|
|
|
private SimpleCache<String, Object> singletonCache; // eg. for cmisRootNodeRef, cmisRenditionMapping
|
|
private final String KEY_CMIS_ROOT_NODEREF = "key.cmisRoot.noderef";
|
|
private final String KEY_CMIS_RENDITION_MAPPING_NODEREF = "key.cmisRenditionMapping.noderef";
|
|
|
|
private String proxyUser;
|
|
private boolean openHttpSession = false;
|
|
|
|
// OpenCMIS objects
|
|
private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS;
|
|
private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH;
|
|
private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS;
|
|
private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH;
|
|
|
|
private List<PermissionDefinition> repositoryPermissions;
|
|
private Map<String, PermissionMapping> permissionMappings;
|
|
|
|
private ObjectFilter objectFilter;
|
|
|
|
// Bulk update properties
|
|
private int bulkMaxItems = 1000;
|
|
private int bulkBatchSize = 20;
|
|
private int bulkWorkerThreads = 2;
|
|
|
|
// --------------------------------------------------------------
|
|
// Configuration
|
|
// --------------------------------------------------------------
|
|
|
|
public void setObjectFilter(ObjectFilter objectFilter)
|
|
{
|
|
this.objectFilter = objectFilter;
|
|
}
|
|
|
|
/**
|
|
* Sets the root store.
|
|
*
|
|
* @param store
|
|
* store_type://store_id
|
|
*/
|
|
public void setStore(String store)
|
|
{
|
|
this.storeRef = new StoreRef(store);
|
|
}
|
|
|
|
public void setSiteService(SiteService siteService)
|
|
{
|
|
this.siteService = siteService;
|
|
}
|
|
|
|
public void setActivityPoster(CmisActivityPoster activityPoster)
|
|
{
|
|
this.activityPoster = activityPoster;
|
|
}
|
|
|
|
public CmisActivityPoster getActivityPoster()
|
|
{
|
|
return activityPoster;
|
|
}
|
|
|
|
public void setHiddenAspect(HiddenAspect hiddenAspect)
|
|
{
|
|
this.hiddenAspect = hiddenAspect;
|
|
}
|
|
|
|
public boolean isHidden(NodeRef nodeRef)
|
|
{
|
|
final FileFilterMode.Client client = FileFilterMode.getClient();
|
|
return (hiddenAspect.getVisibility(client, nodeRef) == Visibility.NotVisible);
|
|
}
|
|
|
|
/**
|
|
* Sets the root path.
|
|
*
|
|
* @param path
|
|
* path within default store
|
|
*/
|
|
public void setRootPath(String path)
|
|
{
|
|
rootPath = path;
|
|
}
|
|
|
|
public BigInteger getTypesDefaultMaxItems()
|
|
{
|
|
return typesDefaultMaxItems;
|
|
}
|
|
|
|
public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems)
|
|
{
|
|
this.typesDefaultMaxItems = typesDefaultMaxItems;
|
|
}
|
|
|
|
public BigInteger getTypesDefaultDepth()
|
|
{
|
|
return typesDefaultDepth;
|
|
}
|
|
|
|
public void setTypesDefaultDepth(BigInteger typesDefaultDepth)
|
|
{
|
|
this.typesDefaultDepth = typesDefaultDepth;
|
|
}
|
|
|
|
public BigInteger getObjectsDefaultMaxItems()
|
|
{
|
|
return objectsDefaultMaxItems;
|
|
}
|
|
|
|
public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems)
|
|
{
|
|
this.objectsDefaultMaxItems = objectsDefaultMaxItems;
|
|
}
|
|
|
|
public BigInteger getObjectsDefaultDepth()
|
|
{
|
|
return objectsDefaultDepth;
|
|
}
|
|
|
|
public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth)
|
|
{
|
|
this.objectsDefaultDepth = objectsDefaultDepth;
|
|
}
|
|
|
|
/**
|
|
* Set rendition kind mapping.
|
|
*/
|
|
public void setRenditionKindMapping(Map<String, List<String>> renditionKinds)
|
|
{
|
|
this.kindToRenditionNames = renditionKinds;
|
|
}
|
|
|
|
public void setOpenHttpSession(boolean openHttpSession)
|
|
{
|
|
this.openHttpSession = openHttpSession;
|
|
}
|
|
|
|
public boolean openHttpSession()
|
|
{
|
|
return openHttpSession;
|
|
}
|
|
|
|
public void setThumbnailService(ThumbnailService thumbnailService)
|
|
{
|
|
this.thumbnailService = thumbnailService;
|
|
}
|
|
|
|
public void setServiceRegistry(ServiceRegistry serviceRegistry)
|
|
{
|
|
this.serviceRegistry = serviceRegistry;
|
|
}
|
|
|
|
/**
|
|
* Sets the descriptor service.
|
|
*/
|
|
public void setDescriptorService(DescriptorService descriptorService)
|
|
{
|
|
this.descriptorService = descriptorService;
|
|
}
|
|
|
|
public DescriptorService getDescriptorService()
|
|
{
|
|
return descriptorService;
|
|
}
|
|
|
|
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
|
|
{
|
|
this.behaviourFilter = behaviourFilter;
|
|
}
|
|
|
|
/**
|
|
* Sets the node service.
|
|
*/
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public NodeService getNodeService()
|
|
{
|
|
return nodeService;
|
|
}
|
|
|
|
public void setActionService(ActionService actionService)
|
|
{
|
|
this.actionService = actionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the version service.
|
|
*/
|
|
public void setVersionService(VersionService versionService)
|
|
{
|
|
this.versionService = versionService;
|
|
}
|
|
|
|
public VersionService getVersionService()
|
|
{
|
|
return versionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the checkOut/checkIn service.
|
|
*/
|
|
public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService)
|
|
{
|
|
this.checkOutCheckInService = checkOutCheckInService;
|
|
}
|
|
|
|
public CheckOutCheckInService getCheckOutCheckInService()
|
|
{
|
|
return checkOutCheckInService;
|
|
}
|
|
|
|
/**
|
|
* Sets the lock service.
|
|
*/
|
|
public LockService getLockService()
|
|
{
|
|
return lockService;
|
|
}
|
|
|
|
public void setLockService(LockService lockService)
|
|
{
|
|
this.lockService = lockService;
|
|
}
|
|
|
|
/**
|
|
* Sets the content service.
|
|
*/
|
|
public void setContentService(ContentService contentService)
|
|
{
|
|
this.contentService = contentService;
|
|
}
|
|
|
|
public ContentService getContentService()
|
|
{
|
|
return contentService;
|
|
}
|
|
|
|
/**
|
|
* Sets the event publisher
|
|
*/
|
|
public void setEventPublisher(EventPublisher eventPublisher)
|
|
{
|
|
this.eventPublisher = eventPublisher;
|
|
}
|
|
|
|
/**
|
|
* Sets the rendition service.
|
|
*/
|
|
public void setrenditionService(RenditionService renditionService)
|
|
{
|
|
this.renditionService = renditionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the file folder service.
|
|
*/
|
|
public void setFileFolderService(FileFolderService fileFolderService)
|
|
{
|
|
this.fileFolderService = fileFolderService;
|
|
}
|
|
|
|
public FileFolderService getFileFolderService()
|
|
{
|
|
return fileFolderService;
|
|
}
|
|
|
|
/**
|
|
* Sets the tenant admin service.
|
|
*/
|
|
public void setTenantAdminService(TenantAdminService tenantAdminService)
|
|
{
|
|
this.tenantAdminService = tenantAdminService;
|
|
}
|
|
|
|
public void setSingletonCache(SimpleCache<String, Object> singletonCache)
|
|
{
|
|
this.singletonCache = singletonCache;
|
|
}
|
|
|
|
/**
|
|
* Sets the transaction service.
|
|
*/
|
|
public void setTransactionService(TransactionService transactionService)
|
|
{
|
|
this.transactionService = transactionService;
|
|
}
|
|
|
|
public TransactionService getTransactionService()
|
|
{
|
|
return transactionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the authentication service.
|
|
*/
|
|
public void setAuthenticationService(AuthenticationService authenticationService)
|
|
{
|
|
this.authenticationService = authenticationService;
|
|
}
|
|
|
|
public AuthenticationService getAuthenticationService()
|
|
{
|
|
return authenticationService;
|
|
}
|
|
|
|
/**
|
|
* Sets the permission service.
|
|
*/
|
|
public void setPermissionService(PermissionService permissionService)
|
|
{
|
|
this.permissionService = permissionService;
|
|
}
|
|
|
|
/**
|
|
* Sets the permission model DAO.
|
|
*/
|
|
public void setPermissionModelDao(ModelDAO permissionModelDao)
|
|
{
|
|
this.permissionModelDao = permissionModelDao;
|
|
}
|
|
|
|
public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService)
|
|
{
|
|
this.cmisDictionaryService = cmisDictionaryService;
|
|
}
|
|
|
|
public void setOpenCMISDictionaryService11(CMISDictionaryService cmisDictionaryService)
|
|
{
|
|
this.cmisDictionaryService11 = cmisDictionaryService;
|
|
}
|
|
|
|
public CMISDictionaryService getOpenCMISDictionaryService()
|
|
{
|
|
CmisVersion cmisVersion = getRequestCmisVersion();
|
|
if(cmisVersion.equals(CmisVersion.CMIS_1_0))
|
|
{
|
|
return cmisDictionaryService;
|
|
}
|
|
else
|
|
{
|
|
return cmisDictionaryService11;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the OpenCMIS query service.
|
|
*/
|
|
public void setOpenCMISQueryService(CMISQueryService cmisQueryService)
|
|
{
|
|
this.cmisQueryService = cmisQueryService;
|
|
}
|
|
|
|
public void setOpenCMISQueryService11(CMISQueryService cmisQueryService)
|
|
{
|
|
this.cmisQueryService11 = cmisQueryService;
|
|
}
|
|
|
|
public CMISQueryService getOpenCMISQueryService()
|
|
{
|
|
CmisVersion cmisVersion = getRequestCmisVersion();
|
|
if(cmisVersion.equals(CmisVersion.CMIS_1_0))
|
|
{
|
|
return cmisQueryService;
|
|
}
|
|
else
|
|
{
|
|
return cmisQueryService11;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the MIME type service.
|
|
*/
|
|
public void setMimetypeService(MimetypeService mimetypeService)
|
|
{
|
|
this.mimetypeService = mimetypeService;
|
|
}
|
|
|
|
public MimetypeService getMimetypeService()
|
|
{
|
|
return mimetypeService;
|
|
}
|
|
|
|
/**
|
|
* Sets the audit service.
|
|
*/
|
|
public void setAuditService(AuditService auditService)
|
|
{
|
|
this.auditService = auditService;
|
|
}
|
|
|
|
/**
|
|
* Sets the namespace service.
|
|
*/
|
|
public void setNamespaceService(NamespaceService namespaceService)
|
|
{
|
|
this.namespaceService = namespaceService;
|
|
}
|
|
|
|
/**
|
|
* Sets the search service.
|
|
*/
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
public SearchService getSearchService()
|
|
{
|
|
return searchService;
|
|
}
|
|
|
|
public void setDictionaryService(DictionaryService dictionaryService)
|
|
{
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
public DictionaryService getDictionaryService()
|
|
{
|
|
return dictionaryService;
|
|
}
|
|
|
|
/**
|
|
* Not implemented
|
|
* @throws UnsupportedOperationException always
|
|
*/
|
|
public void setProxyUser(String proxyUser)
|
|
{
|
|
// this.proxyUser = proxyUser;
|
|
throw new UnsupportedOperationException("proxyUser setting not implemented. Please raise a JIRA request.");
|
|
}
|
|
|
|
public String getProxyUser()
|
|
{
|
|
return proxyUser;
|
|
}
|
|
|
|
/**
|
|
* Sets bulk update properties.
|
|
*/
|
|
public void setBulkMaxItems(int size)
|
|
{
|
|
bulkMaxItems = size;
|
|
}
|
|
|
|
public int getBulkMaxItems()
|
|
{
|
|
return bulkMaxItems;
|
|
}
|
|
|
|
public void setBulkBatchSize(int size)
|
|
{
|
|
bulkBatchSize = size;
|
|
}
|
|
|
|
public int getBulkBatchSize()
|
|
{
|
|
return bulkBatchSize;
|
|
}
|
|
|
|
public void setBulkWorkerThreads(int threads)
|
|
{
|
|
bulkWorkerThreads = threads;
|
|
}
|
|
|
|
public int getBulkWorkerThreads()
|
|
{
|
|
return bulkWorkerThreads;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// Lifecycle methods
|
|
// --------------------------------------------------------------
|
|
|
|
private File tmp;
|
|
|
|
public void setup()
|
|
{
|
|
File tempDir = TempFileProvider.getTempDir();
|
|
this.tmp = new File(tempDir, "CMISAppend");
|
|
if(!this.tmp.exists() && !this.tmp.mkdir())
|
|
{
|
|
throw new AlfrescoRuntimeException("Failed to create CMIS temporary directory");
|
|
}
|
|
}
|
|
|
|
public void init()
|
|
{
|
|
// register as tenant deployer
|
|
tenantAdminService.register(this);
|
|
|
|
// set up and cache rendition mapping
|
|
getRenditionMapping();
|
|
|
|
// cache root node ref
|
|
getRootNodeRef();
|
|
|
|
// cache permission definitions and permission mappings
|
|
repositoryPermissions = getRepositoryPermissions();
|
|
permissionMappings = getPermissionMappings();
|
|
}
|
|
|
|
public void destroy()
|
|
{
|
|
singletonCache.remove(KEY_CMIS_ROOT_NODEREF);
|
|
singletonCache.remove(KEY_CMIS_RENDITION_MAPPING_NODEREF);
|
|
}
|
|
|
|
public void onEnableTenant()
|
|
{
|
|
init();
|
|
}
|
|
|
|
public void onDisableTenant()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
|
{
|
|
lifecycle.setApplicationContext(applicationContext);
|
|
}
|
|
|
|
public void onApplicationEvent(ApplicationContextEvent event)
|
|
{
|
|
lifecycle.onApplicationEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Hooks into Spring Application Lifecycle.
|
|
*/
|
|
private class ProcessorLifecycle extends AbstractLifecycleBean
|
|
{
|
|
@Override
|
|
protected void onBootstrap(ApplicationEvent event)
|
|
{
|
|
init();
|
|
}
|
|
|
|
@Override
|
|
protected void onShutdown(ApplicationEvent event)
|
|
{
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// Alfresco methods
|
|
// --------------------------------------------------------------
|
|
|
|
/*
|
|
* For the given cmis property name get the corresponding Alfresco property name.
|
|
*
|
|
* Certain CMIS properties (e.g. cmis:creationDate and cmis:lastModifiedBy) don't
|
|
* have direct mappings to Alfresco properties through the CMIS dictionary, because
|
|
* these mappings should not be exposed outside the repository through CMIS. For these,
|
|
* however, this method provides the mapping so that the sort works.
|
|
*
|
|
*/
|
|
public Pair<QName, Boolean> getSortProperty(String cmisPropertyName, String direction)
|
|
{
|
|
QName sortPropName = null;
|
|
Pair<QName, Boolean> sortProp = null;
|
|
|
|
PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findPropertyByQueryName(cmisPropertyName);
|
|
if (propDef != null)
|
|
{
|
|
if (propDef.getPropertyId().equals(PropertyIds.BASE_TYPE_ID))
|
|
{
|
|
// special-case (see also ALF-13968) - for getChildren, using "cmis:baseTypeId" allows sorting of folders first and vice-versa (cmis:folder <-> cmis:document)
|
|
sortPropName = GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER;
|
|
}
|
|
else
|
|
{
|
|
sortPropName = propDef.getPropertyAccessor().getMappedProperty();
|
|
}
|
|
|
|
if (sortPropName == null)
|
|
{
|
|
// ok to map these properties because we are always getting current versions of nodes
|
|
sortPropName = SORT_PROPERTY_MAPPINGS.get(cmisPropertyName);
|
|
}
|
|
}
|
|
|
|
if (sortPropName != null)
|
|
{
|
|
boolean sortAsc = (direction == null ? true : direction.equalsIgnoreCase("asc"));
|
|
sortProp = new Pair<QName, Boolean>(sortPropName, sortAsc);
|
|
}
|
|
else
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Ignore sort property '" + cmisPropertyName + " - mapping not found");
|
|
}
|
|
}
|
|
|
|
return sortProp;
|
|
}
|
|
|
|
/**
|
|
* Asynchronously generates thumbnails for the given node.
|
|
*
|
|
* @param nodeRef
|
|
*/
|
|
public void createThumbnails(NodeRef nodeRef, Set<String> thumbnailNames)
|
|
{
|
|
if(thumbnailNames == null || thumbnailNames.size() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
|
|
|
|
// If there's nothing currently registered to generate thumbnails for the
|
|
// specified mimetype, then log a message and bail out
|
|
Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
|
|
ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
|
|
if (contentData == null)
|
|
{
|
|
logger.info("Unable to create thumbnails as there is no content");
|
|
return;
|
|
}
|
|
|
|
long size = contentData.getSize();
|
|
String mimeType = contentData.getMimetype();
|
|
|
|
for(String thumbnailName : thumbnailNames)
|
|
{
|
|
// Use the thumbnail registy to get the details of the thumbail
|
|
ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName);
|
|
if(details == null)
|
|
{
|
|
// Throw exception
|
|
logger.warn("The thumbnail name '" + thumbnailName + "' is not registered");
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if(registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), mimeType, size, nodeRef, details))
|
|
{
|
|
org.alfresco.service.cmr.action.Action action = ThumbnailHelper.createCreateThumbnailAction(details, serviceRegistry);
|
|
|
|
// Queue async creation of thumbnail
|
|
actionService.executeAction(action, nodeRef, true, true);
|
|
}
|
|
else
|
|
{
|
|
logger.info("Unable to create thumbnail '" + details.getName() + "' for " +
|
|
mimeType + " as no transformer is currently available");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts metadata for the node.
|
|
*
|
|
* @param nodeRef
|
|
*/
|
|
public void extractMetadata(NodeRef nodeRef)
|
|
{
|
|
org.alfresco.service.cmr.action.Action action = actionService.createAction(ContentMetadataExtracter.EXECUTOR_NAME);
|
|
actionService.executeAction(action, nodeRef, true, false);
|
|
}
|
|
|
|
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)
|
|
{
|
|
boolean wasEnabled = behaviourFilter.isEnabled(className);
|
|
if(wasEnabled)
|
|
{
|
|
behaviourFilter.disableBehaviour(className);
|
|
}
|
|
return wasEnabled;
|
|
}
|
|
|
|
public boolean enableBehaviour(QName className)
|
|
{
|
|
boolean isEnabled = behaviourFilter.isEnabled(className);
|
|
if(!isEnabled)
|
|
{
|
|
behaviourFilter.enableBehaviour(className);
|
|
}
|
|
return isEnabled;
|
|
}
|
|
|
|
|
|
public boolean disableBehaviour(QName className, NodeRef nodeRef)
|
|
{
|
|
boolean wasEnabled = behaviourFilter.isEnabled(nodeRef, className);
|
|
if(wasEnabled)
|
|
{
|
|
behaviourFilter.disableBehaviour(nodeRef, className);
|
|
}
|
|
return wasEnabled;
|
|
}
|
|
|
|
public boolean enableBehaviour(QName className, NodeRef nodeRef)
|
|
{
|
|
boolean isEnabled = behaviourFilter.isEnabled(nodeRef, className);
|
|
if(!isEnabled)
|
|
{
|
|
behaviourFilter.enableBehaviour(nodeRef, className);
|
|
}
|
|
return isEnabled;
|
|
}
|
|
|
|
public StoreRef getRootStoreRef()
|
|
{
|
|
return getRootNodeRef().getStoreRef();
|
|
}
|
|
|
|
public void deleteNode(NodeRef nodeRef, boolean postActivity)
|
|
{
|
|
ActivityInfo activityInfo = null;
|
|
|
|
// post activity after removal of the node
|
|
postActivity &= !hiddenAspect.hasHiddenAspect(nodeRef);
|
|
if(postActivity)
|
|
{
|
|
// get this information before the node is deleted
|
|
activityInfo = activityPoster.getActivityInfo(nodeRef);
|
|
}
|
|
|
|
getNodeService().deleteNode(nodeRef);
|
|
|
|
// post activity after removal of the node
|
|
if(postActivity && activityInfo != null)
|
|
{
|
|
activityPoster.postFileFolderDeleted(activityInfo);
|
|
}
|
|
}
|
|
|
|
public RetryingTransactionHelper getRetryingTransactionHelper()
|
|
{
|
|
return transactionService.getRetryingTransactionHelper();
|
|
}
|
|
|
|
/**
|
|
* Returns the root folder node ref.
|
|
*/
|
|
public NodeRef getRootNodeRef()
|
|
{
|
|
NodeRef rootNodeRef = (NodeRef)singletonCache.get(KEY_CMIS_ROOT_NODEREF);
|
|
if (rootNodeRef == null)
|
|
{
|
|
rootNodeRef = AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
|
|
{
|
|
public NodeRef doWork() throws Exception
|
|
{
|
|
return transactionService.getRetryingTransactionHelper().doInTransaction(
|
|
new RetryingTransactionCallback<NodeRef>()
|
|
{
|
|
public NodeRef execute() throws Exception
|
|
{
|
|
NodeRef root = nodeService.getRootNode(storeRef);
|
|
List<NodeRef> rootNodes = searchService.selectNodes(root, rootPath, null,
|
|
namespaceService, false);
|
|
if (rootNodes.size() != 1)
|
|
{
|
|
throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath);
|
|
}
|
|
return rootNodes.get(0);
|
|
};
|
|
}, true);
|
|
}
|
|
}, AuthenticationUtil.getSystemUserName());
|
|
|
|
if (rootNodeRef == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!");
|
|
}
|
|
|
|
singletonCache.put(KEY_CMIS_ROOT_NODEREF, rootNodeRef);
|
|
}
|
|
|
|
return rootNodeRef;
|
|
}
|
|
|
|
public String getName(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
|
|
return (name instanceof String ? (String) name : null);
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cuts of the version information from an object id.
|
|
*/
|
|
public String getCurrentVersionId(String objectId)
|
|
{
|
|
if (objectId == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int sepIndex = objectId.lastIndexOf(ID_SEPERATOR);
|
|
if (sepIndex > -1)
|
|
{
|
|
return objectId.substring(0, sepIndex);
|
|
}
|
|
|
|
return objectId;
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(String objectId)
|
|
{
|
|
return new CMISNodeInfoImpl(this, objectId);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef)
|
|
{
|
|
return createNodeInfo(nodeRef, null);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(NodeRef nodeRef, VersionHistory versionHistory)
|
|
{
|
|
return new CMISNodeInfoImpl(this, nodeRef, versionHistory);
|
|
}
|
|
|
|
/**
|
|
* Creates an object info object.
|
|
*/
|
|
public CMISNodeInfoImpl createNodeInfo(AssociationRef assocRef)
|
|
{
|
|
return new CMISNodeInfoImpl(this, assocRef);
|
|
}
|
|
|
|
/*
|
|
* Strip store ref from the id, if there is one.
|
|
*/
|
|
private String getGuid(String id)
|
|
{
|
|
int idx = id.lastIndexOf("/");
|
|
if(idx != -1)
|
|
{
|
|
return id.substring(idx + 1);
|
|
}
|
|
else
|
|
{
|
|
return id;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Construct an object id based on the incoming assocRef and versionLabel. The object id will always
|
|
* be the assocRef guid.
|
|
*/
|
|
public String constructObjectId(AssociationRef assocRef, String versionLabel)
|
|
{
|
|
return constructObjectId(assocRef, versionLabel, isPublicApi());
|
|
}
|
|
|
|
public String constructObjectId(AssociationRef assocRef, String versionLabel, boolean dropStoreRef)
|
|
{
|
|
StringBuilder sb = new StringBuilder(CMISConnector.ASSOC_ID_PREFIX);
|
|
if(dropStoreRef)
|
|
{
|
|
// always return the guid
|
|
sb.append(assocRef.getId());
|
|
}
|
|
else
|
|
{
|
|
sb.append(assocRef.toString());
|
|
}
|
|
|
|
if(versionLabel != null)
|
|
{
|
|
sb.append(CMISConnector.ID_SEPERATOR);
|
|
sb.append(versionLabel);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/*
|
|
* Construct an object id based on the incoming incomingObjectId. The object id will always
|
|
* be the node guid.
|
|
*/
|
|
public String constructObjectId(String incomingObjectId)
|
|
{
|
|
return constructObjectId(incomingObjectId, null);
|
|
}
|
|
|
|
/*
|
|
* Construct an object id based on the incoming incomingNodeId and versionLabel. The object id will always
|
|
* be the node guid.
|
|
*/
|
|
public String constructObjectId(String incomingNodeId, String versionLabel)
|
|
{
|
|
return constructObjectId(incomingNodeId, versionLabel, isPublicApi());
|
|
}
|
|
|
|
public String constructObjectId(String incomingNodeId, String versionLabel, boolean dropStoreRef)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
if(dropStoreRef)
|
|
{
|
|
// always return the guid
|
|
sb.append(getGuid(incomingNodeId));
|
|
}
|
|
else
|
|
{
|
|
sb.append(incomingNodeId);
|
|
}
|
|
if(versionLabel != null)
|
|
{
|
|
sb.append(CMISConnector.ID_SEPERATOR);
|
|
sb.append(versionLabel);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public void createVersion(NodeRef nodeRef, VersionType versionType, String reason)
|
|
{
|
|
if(versionService.getVersionHistory(nodeRef) == null)
|
|
{
|
|
// no version history. Make sure we have an initial major version 1.0.
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(2);
|
|
versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial version");
|
|
versionService.createVersion(nodeRef, versionProperties);
|
|
}
|
|
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(2);
|
|
versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType);
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, reason);
|
|
versionService.createVersion(nodeRef, versionProperties);
|
|
}
|
|
|
|
public CmisVersion getRequestCmisVersion()
|
|
{
|
|
CallContext callContext = AlfrescoCmisServiceCall.get();
|
|
CmisVersion cmisVersion = (callContext != null ? callContext.getCmisVersion() : CmisVersion.CMIS_1_0);
|
|
return cmisVersion;
|
|
}
|
|
|
|
private boolean isPublicApi()
|
|
{
|
|
boolean isPublicApi = false;
|
|
CallContext callContext = AlfrescoCmisServiceCall.get();
|
|
if(callContext != null)
|
|
{
|
|
String value = (String)callContext.get("isPublicApi");
|
|
isPublicApi = (value == null ? false : Boolean.parseBoolean(value));
|
|
}
|
|
return isPublicApi;
|
|
}
|
|
|
|
/*
|
|
* Construct an object id based on the incoming incomingNodeRef and versionLabel. The object id will always
|
|
* be the incomingNodeRef guid.
|
|
*/
|
|
public String constructObjectId(NodeRef incomingNodeRef, String versionLabel)
|
|
{
|
|
return constructObjectId(incomingNodeRef, versionLabel, isPublicApi());
|
|
}
|
|
|
|
public String constructObjectId(NodeRef incomingNodeRef, String versionLabel, boolean dropStoreRef)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append(dropStoreRef ? incomingNodeRef.getId() : incomingNodeRef.toString());
|
|
if(versionLabel != null)
|
|
{
|
|
sb.append(CMISConnector.ID_SEPERATOR);
|
|
sb.append(versionLabel);
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Compiles a CMIS object if for a live node.
|
|
*/
|
|
public String createObjectId(NodeRef nodeRef)
|
|
{
|
|
return createObjectId(nodeRef, isPublicApi());
|
|
}
|
|
|
|
public String createObjectId(NodeRef nodeRef, boolean dropStoreRef)
|
|
{
|
|
QName typeQName = nodeService.getType(nodeRef);
|
|
TypeDefinitionWrapper type = getOpenCMISDictionaryService().findNodeType(typeQName);
|
|
|
|
if(type instanceof ItemTypeDefinitionWrapper)
|
|
{
|
|
return constructObjectId(nodeRef, null);
|
|
}
|
|
|
|
if(type instanceof FolderTypeDefintionWrapper)
|
|
{
|
|
return constructObjectId(nodeRef, null, dropStoreRef);
|
|
}
|
|
|
|
Serializable versionLabel = getNodeService()
|
|
.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL);
|
|
if (versionLabel == null)
|
|
{
|
|
versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL;
|
|
}
|
|
|
|
return constructObjectId(nodeRef, (String)versionLabel, dropStoreRef);
|
|
}
|
|
|
|
private boolean isFolder(NodeRef nodeRef)
|
|
{
|
|
return getType(nodeRef) instanceof FolderTypeDefintionWrapper;
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of a node or <code>null</code> if no type
|
|
* definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
QName typeQName = nodeService.getType(nodeRef);
|
|
return getType(typeQName);
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private TypeDefinitionWrapper getType(QName typeQName)
|
|
{
|
|
return getOpenCMISDictionaryService().findNodeType(typeQName);
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of an association or <code>null</code> if no
|
|
* type definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(AssociationRef assocRef)
|
|
{
|
|
QName typeQName = assocRef.getTypeQName();
|
|
return getOpenCMISDictionaryService().findAssocType(typeQName);
|
|
}
|
|
|
|
/**
|
|
* Returns the type definition of a node or <code>null</code> if no type
|
|
* definition could be found.
|
|
*/
|
|
public TypeDefinitionWrapper getType(String cmisTypeId)
|
|
{
|
|
return getOpenCMISDictionaryService().findType(cmisTypeId);
|
|
}
|
|
|
|
/**
|
|
* Returns the definition after it has checked if the type can be used for
|
|
* object creation.
|
|
*/
|
|
public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId)
|
|
{
|
|
TypeDefinitionWrapper type = getType(cmisTypeId);
|
|
if ((type == null) || (type.getBaseTypeId() != baseTypeId))
|
|
{
|
|
switch (baseTypeId)
|
|
{
|
|
case CMIS_DOCUMENT:
|
|
throw new CmisConstraintException("Type is not a document type!");
|
|
case CMIS_FOLDER:
|
|
throw new CmisConstraintException("Type is not a folder type!");
|
|
case CMIS_RELATIONSHIP:
|
|
throw new CmisConstraintException("Type is not a relationship type!");
|
|
case CMIS_POLICY:
|
|
throw new CmisConstraintException("Type is not a policy type!");
|
|
case CMIS_ITEM:
|
|
throw new CmisConstraintException("Type is not an item type!");
|
|
}
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isCreatable())
|
|
{
|
|
throw new CmisConstraintException("Type is not creatable!");
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Applies a versioning state to a document.
|
|
*/
|
|
public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState)
|
|
{
|
|
if (versioningState == VersioningState.CHECKEDOUT)
|
|
{
|
|
if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_INITIAL_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION_PROPS, false);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
|
|
}
|
|
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(5);
|
|
versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR);
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version");
|
|
|
|
getCheckOutCheckInService().checkout(nodeRef);
|
|
}
|
|
else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR))
|
|
{
|
|
if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_INITIAL_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION, false);
|
|
props.put(ContentModel.PROP_AUTO_VERSION_PROPS, false);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
|
|
}
|
|
|
|
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(5);
|
|
versionProperties.put(
|
|
VersionModel.PROP_VERSION_TYPE,
|
|
versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR);
|
|
versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version");
|
|
|
|
versionService.createVersion(nodeRef, versionProperties);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a child of a given type can be added to a given folder.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public void checkChildObjectType(CMISNodeInfo folderInfo, String childType)
|
|
{
|
|
TypeDefinitionWrapper targetType = folderInfo.getType();
|
|
PropertyDefinitionWrapper allowableChildObjectTypeProperty = targetType
|
|
.getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS);
|
|
List<String> childTypes = (List<String>) allowableChildObjectTypeProperty.getPropertyAccessor().getValue(
|
|
folderInfo);
|
|
|
|
if ((childTypes == null) || childTypes.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!childTypes.contains(childType))
|
|
{
|
|
throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the CMIS object for a node.
|
|
*/
|
|
public ObjectData createCMISObject(CMISNodeInfo info, FileInfo node, String filter,
|
|
boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
boolean includePolicyIds, boolean includeAcl)
|
|
{
|
|
if (info.getType() == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
Properties nodeProps = getNodeProperties(info, node, filter, info.getType());
|
|
|
|
return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, includePolicyIds, includeAcl);
|
|
}
|
|
|
|
public ObjectData createCMISObject(CMISNodeInfo info, String filter, boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds,
|
|
boolean includeAcl)
|
|
{
|
|
if (info.getType() == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
|
|
Properties nodeProps = (info.isRelationship() ? getAssocProperties(info, filter) : getNodeProperties(info,
|
|
filter));
|
|
|
|
return createCMISObjectImpl(info, nodeProps, filter, includeAllowableActions, includeRelationships,
|
|
renditionFilter, includePolicyIds, includeAcl);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private ObjectData createCMISObjectImpl(final CMISNodeInfo info, Properties nodeProps, String filter,
|
|
boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
|
|
boolean includePolicyIds, boolean includeAcl)
|
|
{
|
|
final ObjectDataImpl result = new ObjectDataImpl();
|
|
|
|
// set allowable actions
|
|
if (includeAllowableActions)
|
|
{
|
|
result.setAllowableActions(getAllowableActions(info));
|
|
}
|
|
|
|
// set policy ids
|
|
if (includePolicyIds)
|
|
{
|
|
result.setPolicyIds(new PolicyIdListImpl());
|
|
}
|
|
|
|
if (info.isRelationship())
|
|
{
|
|
// set properties
|
|
result.setProperties(getAssocProperties(info, filter));
|
|
|
|
// set ACL
|
|
if (includeAcl)
|
|
{
|
|
// association have no ACL - return an empty list of ACEs
|
|
result.setAcl(new AccessControlListImpl((List<Ace>) Collections.EMPTY_LIST));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// set properties
|
|
result.setProperties(nodeProps);
|
|
|
|
// set relationships
|
|
if (includeRelationships != IncludeRelationships.NONE)
|
|
{
|
|
result.setRelationships(getRelationships(info.getNodeRef(), includeRelationships));
|
|
}
|
|
|
|
// set renditions
|
|
if (!RENDITION_NONE.equals(renditionFilter))
|
|
{
|
|
List<RenditionData> renditions = getRenditions(info.getNodeRef(), renditionFilter, null, null);
|
|
if ((renditions != null) && (!renditions.isEmpty()))
|
|
{
|
|
result.setRenditions(renditions);
|
|
}
|
|
else
|
|
{
|
|
result.setRenditions(Collections.EMPTY_LIST);
|
|
}
|
|
}
|
|
|
|
// set ACL
|
|
if (includeAcl)
|
|
{
|
|
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
|
|
{
|
|
@Override
|
|
public Void doWork() throws Exception
|
|
{
|
|
result.setAcl(getACL(info.getCurrentNodeNodeRef(), false));
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
// add aspects
|
|
List<CmisExtensionElement> extensions = getAspectExtensions(info, filter, result.getProperties()
|
|
.getProperties().keySet());
|
|
if (!extensions.isEmpty())
|
|
{
|
|
result.getProperties().setExtensions(
|
|
Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl(
|
|
ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public String getPath(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
Path path = nodeService.getPath(nodeRef);
|
|
|
|
// skip to CMIS root path
|
|
NodeRef rootNode = getRootNodeRef();
|
|
int i = 0;
|
|
while (i < path.size())
|
|
{
|
|
Path.Element element = path.get(i);
|
|
if (element instanceof ChildAssocElement)
|
|
{
|
|
ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
|
|
NodeRef node = assocRef.getChildRef();
|
|
if (node.equals(rootNode))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
StringBuilder displayPath = new StringBuilder(64);
|
|
|
|
if (path.size() - i == 1)
|
|
{
|
|
// render root path
|
|
displayPath.append("/");
|
|
}
|
|
else
|
|
{
|
|
// render CMIS scoped path
|
|
i++;
|
|
while (i < path.size())
|
|
{
|
|
Path.Element element = path.get(i);
|
|
if (element instanceof ChildAssocElement)
|
|
{
|
|
ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef();
|
|
NodeRef node = assocRef.getChildRef();
|
|
displayPath.append("/");
|
|
displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME));
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return displayPath.toString();
|
|
}
|
|
catch(InvalidNodeRefException inre)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the content from the repository.
|
|
*/
|
|
public ContentStream getContentStream(CMISNodeInfo info, String streamId, BigInteger offset, BigInteger length)
|
|
{
|
|
// get the type and check if the object can have content
|
|
TypeDefinitionWrapper type = info.getType();
|
|
checkDocumentTypeForContent(type);
|
|
|
|
// looks like a document, now get the content
|
|
ContentStreamImpl result = new ContentStreamImpl();
|
|
result.setFileName(info.getName());
|
|
|
|
// if streamId is set, fetch other content
|
|
NodeRef streamNodeRef = info.getNodeRef();
|
|
if ((streamId != null) && (streamId.length() > 0))
|
|
{
|
|
CMISNodeInfo streamInfo = createNodeInfo(streamId);
|
|
if (!streamInfo.isVariant(CMISObjectVariant.CURRENT_VERSION))
|
|
{
|
|
throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId + ", expected variant " + CMISObjectVariant.CURRENT_VERSION + ", got variant " + streamInfo.getObjectVariant());
|
|
}
|
|
|
|
streamNodeRef = streamInfo.getNodeRef();
|
|
type = streamInfo.getType();
|
|
checkDocumentTypeForContent(type);
|
|
}
|
|
|
|
// get the stream now
|
|
try
|
|
{
|
|
ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT);
|
|
if (contentReader == null)
|
|
{
|
|
throw new CmisConstraintException("Document has no content!");
|
|
}
|
|
|
|
result.setMimeType(contentReader.getMimetype());
|
|
long contentSize = contentReader.getSize();
|
|
|
|
if ((offset == null) && (length == null))
|
|
{
|
|
result.setStream(contentReader.getContentInputStream());
|
|
result.setLength(BigInteger.valueOf(contentSize));
|
|
publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), null);
|
|
}
|
|
else
|
|
{
|
|
long off = (offset == null ? 0 : offset.longValue());
|
|
long len = (length == null ? contentSize : length.longValue());
|
|
if (off + len > contentSize)
|
|
{
|
|
len = contentReader.getSize() - off;
|
|
}
|
|
|
|
result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len));
|
|
result.setLength(BigInteger.valueOf(len));
|
|
publishReadEvent(streamNodeRef, info.getName(), result.getMimeType(), contentSize, contentReader.getEncoding(), off+" - "+len);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (e instanceof CmisBaseException)
|
|
{
|
|
throw (CmisBaseException) e;
|
|
}
|
|
else
|
|
{
|
|
StringBuilder msg = new StringBuilder("Failed to retrieve content: " + e.getMessage());
|
|
Throwable cause = e.getCause();
|
|
if(cause != null)
|
|
{
|
|
// add the cause to the CMIS exception
|
|
msg.append(", ");
|
|
msg.append(cause.getMessage());
|
|
}
|
|
throw new CmisRuntimeException(msg.toString(), e);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Notifies listeners that a read has taken place.
|
|
*
|
|
* @param streamNodeRef
|
|
* @param type
|
|
* @param mimeType
|
|
* @param contentSize
|
|
* @param encoding
|
|
* @param string
|
|
*/
|
|
protected void publishReadEvent(final NodeRef nodeRef, final String name, final String mimeType, final long contentSize, final String encoding, final String range)
|
|
{
|
|
final QName nodeType = nodeRef==null?null:nodeService.getType(nodeRef);
|
|
|
|
eventPublisher.publishEvent(new EventPreparator(){
|
|
@Override
|
|
public Event prepareEvent(String user, String networkId, String transactionId)
|
|
{
|
|
if (StringUtils.hasText(range))
|
|
{
|
|
return new ContentReadRangeEvent(user, networkId, transactionId,
|
|
nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding, range);
|
|
}
|
|
else
|
|
{
|
|
return new ContentEventImpl(ContentEvent.DOWNLOAD, user, networkId, transactionId,
|
|
nodeRef.getId(), null, nodeType.toString(), Client.asType(ClientType.cmis), name, mimeType, contentSize, encoding);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
}
|
|
|
|
public void appendContent(CMISNodeInfo nodeInfo, ContentStream contentStream, boolean isLastChunk) throws IOException
|
|
{
|
|
NodeRef nodeRef = nodeInfo.getNodeRef();
|
|
|
|
this.disableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef);
|
|
|
|
if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT))
|
|
{
|
|
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
|
|
props.put(ContentModel.PROP_GOT_FIRST_CHUNK, true);
|
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT, props);
|
|
}
|
|
|
|
ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
|
InputStream existingContentInput = (reader != null ? reader.getContentInputStream() : null);
|
|
InputStream input = contentStream.getStream();
|
|
|
|
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
|
|
OutputStream out = new BufferedOutputStream(writer.getContentOutputStream());
|
|
|
|
InputStream in = null;
|
|
if(existingContentInput != null)
|
|
{
|
|
in = new SequenceInputStream(existingContentInput, input);
|
|
}
|
|
else
|
|
{
|
|
in = input;
|
|
}
|
|
|
|
try
|
|
{
|
|
IOUtils.copy(in, out);
|
|
|
|
if(isLastChunk)
|
|
{
|
|
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_CMIS_UPDATE_CONTEXT);
|
|
getActivityPoster().postFileFolderUpdated(nodeInfo.isFolder(), nodeRef);
|
|
|
|
createVersion(nodeRef, VersionType.MINOR, "Appended content stream");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if(in != null)
|
|
{
|
|
in.close();
|
|
}
|
|
if(out != null)
|
|
{
|
|
out.close();
|
|
}
|
|
|
|
this.enableBehaviour(ContentModel.ASPECT_VERSIONABLE, nodeRef);
|
|
}
|
|
}
|
|
|
|
private void checkDocumentTypeForContent(TypeDefinitionWrapper type)
|
|
{
|
|
if (type == null)
|
|
{
|
|
throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?");
|
|
}
|
|
if (!(type instanceof DocumentTypeDefinitionWrapper))
|
|
{
|
|
throw new CmisStreamNotSupportedException("Object is not a document!");
|
|
}
|
|
if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED)
|
|
{
|
|
throw new CmisConstraintException("Document cannot have content!");
|
|
}
|
|
}
|
|
|
|
private void addAspectProperties(CMISNodeInfo info, String filter, PropertiesImpl result)
|
|
{
|
|
if(getRequestCmisVersion().equals(CmisVersion.CMIS_1_1))
|
|
{
|
|
Set<String> propertyIds = new HashSet<String>();
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
Set<QName> aspects = nodeService.getAspects(info.getNodeRef());
|
|
for (QName aspect : aspects)
|
|
{
|
|
TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect);
|
|
if (aspectType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (PropertyDefinitionWrapper propDef : aspectType.getProperties())
|
|
{
|
|
if (propertyIds.contains(propDef.getPropertyId()))
|
|
{
|
|
// skip properties that have already been added
|
|
continue;
|
|
}
|
|
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
|
|
Serializable value = propDef.getPropertyAccessor().getValue(info);
|
|
result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
|
|
|
|
// mark property as 'added'
|
|
propertyIds.add(propDef.getPropertyId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Properties getNodeProperties(CMISNodeInfo info, String filter)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
for (PropertyDefinitionWrapper propDef : info.getType().getProperties())
|
|
{
|
|
if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
|
|
{
|
|
// don't filter the object id
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Serializable value = propDef.getPropertyAccessor().getValue(info);
|
|
result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
|
|
}
|
|
|
|
addAspectProperties(info, filter, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
public Properties getNodeProperties(CMISNodeInfo info, FileInfo node, String filter, TypeDefinitionWrapper type)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
Map<QName, Serializable> nodeProps = node.getProperties();
|
|
|
|
for (PropertyDefinitionWrapper propDef : type.getProperties())
|
|
{
|
|
if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID))
|
|
{
|
|
// don't filter the object id
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Serializable value = null;
|
|
|
|
CMISPropertyAccessor accessor = propDef.getPropertyAccessor();
|
|
if (accessor instanceof DirectProperty)
|
|
{
|
|
value = nodeProps.get(accessor.getMappedProperty());
|
|
}
|
|
else
|
|
{
|
|
value = propDef.getPropertyAccessor().getValue(info);
|
|
}
|
|
|
|
result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value));
|
|
}
|
|
|
|
addAspectProperties(info, filter, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
public Properties getAssocProperties(CMISNodeInfo info, String filter)
|
|
{
|
|
PropertiesImpl result = new PropertiesImpl();
|
|
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
for (PropertyDefinitionWrapper propDefWrap : info.getType().getProperties())
|
|
{
|
|
PropertyDefinition<?> propDef = propDefWrap.getPropertyDefinition();
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
|
|
CMISPropertyAccessor cmisPropertyAccessor = propDefWrap.getPropertyAccessor();
|
|
Serializable value = cmisPropertyAccessor.getValue(info);
|
|
PropertyType propType = propDef.getPropertyType();
|
|
PropertyData<?> propertyData = getProperty(propType, propDefWrap, value);
|
|
result.addProperty(propertyData);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Builds aspect extension.
|
|
*/
|
|
public List<CmisExtensionElement> getAspectExtensions(CMISNodeInfo info, String filter,
|
|
Set<String> alreadySetProperties)
|
|
{
|
|
List<CmisExtensionElement> extensions = new ArrayList<CmisExtensionElement>();
|
|
Set<String> propertyIds = new HashSet<String>(alreadySetProperties);
|
|
List<CmisExtensionElement> propertyExtensionList = new ArrayList<CmisExtensionElement>();
|
|
Set<String> filterSet = splitFilter(filter);
|
|
|
|
Set<QName> aspects = nodeService.getAspects(info.getNodeRef());
|
|
for (QName aspect : aspects)
|
|
{
|
|
TypeDefinitionWrapper aspectType = getOpenCMISDictionaryService().findNodeType(aspect);
|
|
if (aspectType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AspectDefinition aspectDefinition = dictionaryService.getAspect(aspect);
|
|
Map<QName, org.alfresco.service.cmr.dictionary.PropertyDefinition> aspectProperties = aspectDefinition.getProperties();
|
|
|
|
extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType
|
|
.getTypeId()));
|
|
|
|
for (PropertyDefinitionWrapper propDef : aspectType.getProperties())
|
|
{
|
|
boolean addPropertyToExtensionList = getRequestCmisVersion().equals(CmisVersion.CMIS_1_1) && aspectProperties.keySet().contains(propDef.getAlfrescoName());
|
|
// MNT-11876 : add property to extension even if it has been returned (CMIS 1.1)
|
|
if (propertyIds.contains(propDef.getPropertyId()) && !addPropertyToExtensionList)
|
|
{
|
|
// skip properties that have already been added
|
|
continue;
|
|
}
|
|
|
|
if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName())))
|
|
{
|
|
// skip properties that are not in the filter
|
|
continue;
|
|
}
|
|
|
|
Serializable value = propDef.getPropertyAccessor().getValue(info);
|
|
propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value));
|
|
|
|
// mark property as 'added'
|
|
propertyIds.add(propDef.getPropertyId());
|
|
}
|
|
}
|
|
|
|
if (!propertyExtensionList.isEmpty())
|
|
{
|
|
CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl(
|
|
ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList);
|
|
extensions.addAll(Collections.singletonList(propertiesExtension));
|
|
}
|
|
|
|
return extensions;
|
|
}
|
|
|
|
/**
|
|
* Creates a property extension element.
|
|
*/
|
|
@SuppressWarnings("rawtypes")
|
|
private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition<?> propertyDefinition, Object value)
|
|
{
|
|
String name;
|
|
switch (propertyDefinition.getPropertyType())
|
|
{
|
|
case BOOLEAN:
|
|
name = "propertyBoolean";
|
|
break;
|
|
case DATETIME:
|
|
name = "propertyDateTime";
|
|
break;
|
|
case DECIMAL:
|
|
name = "propertyDecimal";
|
|
break;
|
|
case INTEGER:
|
|
name = "propertyInteger";
|
|
break;
|
|
case ID:
|
|
name = "propertyId";
|
|
break;
|
|
default:
|
|
name = "propertyString";
|
|
}
|
|
|
|
Map<String, String> attributes = new HashMap<String, String>();
|
|
attributes.put("propertyDefinitionId", propertyDefinition.getId());
|
|
attributes.put("queryName", propertyDefinition.getQueryName());
|
|
// optional value
|
|
if (propertyDefinition.getDisplayName() !=null && propertyDefinition.getDisplayName().trim().length() > 0)
|
|
{
|
|
attributes.put("displayName", propertyDefinition.getDisplayName());
|
|
}
|
|
// optional value
|
|
if (propertyDefinition.getLocalName() !=null && propertyDefinition.getLocalName().trim().length() > 0)
|
|
{
|
|
attributes.put("localName", propertyDefinition.getLocalName());
|
|
}
|
|
|
|
|
|
List<CmisExtensionElement> propertyValues = new ArrayList<CmisExtensionElement>();
|
|
if (value != null)
|
|
{
|
|
if (value instanceof List)
|
|
{
|
|
for (Object o : ((List) value))
|
|
{
|
|
if(o != null)
|
|
{
|
|
propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
|
|
convertAspectPropertyValue(o)));
|
|
}
|
|
else
|
|
{
|
|
logger.warn("Unexpected null entry in list value for property " + propertyDefinition.getDisplayName()
|
|
+ ", value = " + value);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null,
|
|
convertAspectPropertyValue(value)));
|
|
}
|
|
}
|
|
|
|
return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues);
|
|
}
|
|
|
|
private String convertAspectPropertyValue(Object value)
|
|
{
|
|
if (value instanceof Date)
|
|
{
|
|
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
|
cal.setTime((Date) value);
|
|
value = cal;
|
|
}
|
|
|
|
if (value instanceof GregorianCalendar)
|
|
{
|
|
DatatypeFactory df;
|
|
try
|
|
{
|
|
df = DatatypeFactory.newInstance();
|
|
}
|
|
catch (DatatypeConfigurationException e)
|
|
{
|
|
throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e);
|
|
}
|
|
return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat();
|
|
}
|
|
|
|
// MNT-12496
|
|
// Encode for AtomPub only. Browser/json binding already encodes.
|
|
if (AlfrescoCmisServiceCall.get() != null && CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()))
|
|
{
|
|
return escapeControlCharacters(value.toString());
|
|
}
|
|
else
|
|
{
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private AbstractPropertyData<?> getProperty(PropertyType propType, PropertyDefinitionWrapper propDef,
|
|
Serializable value)
|
|
{
|
|
AbstractPropertyData<?> result = null;
|
|
switch (propType)
|
|
{
|
|
case BOOLEAN:
|
|
result = new PropertyBooleanImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyBooleanImpl) result).setValues((List<Boolean>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyBooleanImpl) result).setValue((Boolean) value);
|
|
}
|
|
break;
|
|
case DATETIME:
|
|
result = new PropertyDateTimeImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyDateTimeImpl) result).setValues((List<GregorianCalendar>) DefaultTypeConverter.INSTANCE
|
|
.convert(GregorianCalendar.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class,
|
|
value));
|
|
}
|
|
break;
|
|
case DECIMAL:
|
|
result = new PropertyDecimalImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyDecimalImpl) result).setValues((List<BigDecimal>) DefaultTypeConverter.INSTANCE.convert(
|
|
BigDecimal.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value));
|
|
}
|
|
break;
|
|
case HTML:
|
|
result = new PropertyHtmlImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyHtmlImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyHtmlImpl) result).setValue((String) value);
|
|
}
|
|
break;
|
|
case ID:
|
|
result = new PropertyIdImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyIdImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
if (value instanceof NodeRef)
|
|
{
|
|
((PropertyIdImpl) result).setValue(constructObjectId((NodeRef)value, null));
|
|
}
|
|
else
|
|
{
|
|
((PropertyIdImpl) result).setValue((String) value);
|
|
}
|
|
}
|
|
break;
|
|
case INTEGER:
|
|
result = new PropertyIntegerImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyIntegerImpl) result).setValues((List<BigInteger>) DefaultTypeConverter.INSTANCE.convert(
|
|
BigInteger.class, (List<?>) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value));
|
|
}
|
|
break;
|
|
case STRING:
|
|
result = new PropertyStringImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyStringImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
// MNT-12496
|
|
// Encode for AtomPub only. Browser/json binding already encodes.
|
|
if (AlfrescoCmisServiceCall.get() != null && CallContext.BINDING_ATOMPUB.equals(AlfrescoCmisServiceCall.get().getBinding()))
|
|
{
|
|
((PropertyStringImpl) result).setValue(escapeControlCharacters((String) value));
|
|
}
|
|
else
|
|
{
|
|
((PropertyStringImpl) result).setValue((String) value);
|
|
}
|
|
}
|
|
break;
|
|
case URI:
|
|
result = new PropertyUriImpl();
|
|
if (value instanceof List)
|
|
{
|
|
((PropertyUriImpl) result).setValues((List<String>) value);
|
|
}
|
|
else
|
|
{
|
|
((PropertyUriImpl) result).setValue((String) value);
|
|
}
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Unknown datatype! Spec change?");
|
|
}
|
|
|
|
if (propDef != null)
|
|
{
|
|
result.setId(propDef.getPropertyDefinition().getId());
|
|
result.setQueryName(propDef.getPropertyDefinition().getQueryName());
|
|
result.setDisplayName(propDef.getPropertyDefinition().getDisplayName());
|
|
result.setLocalName(propDef.getPropertyDefinition().getLocalName());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private String escapeControlCharacters(String origValue)
|
|
{
|
|
if (origValue == null)
|
|
{
|
|
return origValue;
|
|
}
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int i = 0; i < origValue.length(); i++)
|
|
{
|
|
char ch = origValue.charAt(i);
|
|
switch (ch)
|
|
{
|
|
case '\b':
|
|
case '\f':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
sb.append(ch);
|
|
break;
|
|
default:
|
|
// Reference: http://www.unicode.org/versions/Unicode5.1.0/
|
|
if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF'))
|
|
{
|
|
String ss = Integer.toHexString(ch);
|
|
sb.append("\\u");
|
|
for (int k = 0; k < 4 - ss.length(); k++)
|
|
{
|
|
sb.append('0');
|
|
}
|
|
sb.append(ss.toUpperCase());
|
|
}
|
|
else
|
|
{
|
|
sb.append(ch);
|
|
}
|
|
}
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private Set<String> splitFilter(String filter)
|
|
{
|
|
if (filter == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (filter.trim().length() == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Set<String> result = new HashSet<String>();
|
|
for (String s : filter.split(","))
|
|
{
|
|
s = s.trim();
|
|
if (s.equals("*"))
|
|
{
|
|
return null;
|
|
}
|
|
else if (s.length() > 0)
|
|
{
|
|
result.add(s);
|
|
}
|
|
}
|
|
|
|
// set a few base properties
|
|
result.add(QUERY_NAME_OBJECT_ID);
|
|
result.add(QUERY_NAME_OBJECT_TYPE_ID);
|
|
result.add(QUERY_NAME_BASE_TYPE_ID);
|
|
|
|
return result;
|
|
}
|
|
|
|
public AllowableActions getAllowableActions(CMISNodeInfo info)
|
|
{
|
|
AllowableActionsImpl result = new AllowableActionsImpl();
|
|
Set<Action> allowableActions = new HashSet<Action>();
|
|
result.setAllowableActions(allowableActions);
|
|
|
|
for (CMISActionEvaluator evaluator : info.getType().getActionEvaluators().values())
|
|
{
|
|
if (evaluator.isAllowed(info))
|
|
{
|
|
allowableActions.add(evaluator.getAction());
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<ObjectData> getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships/*, CmisVersion cmisVersion*/)
|
|
{
|
|
List<ObjectData> result = new ArrayList<ObjectData>();
|
|
|
|
if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
|
|
{
|
|
// relationships from and to versions are not preserved
|
|
return result;
|
|
}
|
|
|
|
// get relationships
|
|
List<AssociationRef> assocs = new ArrayList<AssociationRef>();
|
|
if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH)
|
|
{
|
|
assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH)
|
|
{
|
|
assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
|
|
// filter relationships that not map the CMIS domain model
|
|
for (AssociationRef assocRef : assocs)
|
|
{
|
|
TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName());
|
|
if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
|
|
|| getType(assocRef.getTargetRef()) == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE,
|
|
RENDITION_NONE, false, false/*, cmisVersion*/));
|
|
}
|
|
catch(AccessDeniedException e)
|
|
{
|
|
// PUBLICAPI-110
|
|
// ok, just skip it
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
// TODO: Somewhere this has not been wrapped correctly
|
|
catch (net.sf.acegisecurity.AccessDeniedException e)
|
|
{
|
|
// skip
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection,
|
|
String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount)
|
|
{
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
result.setHasMoreItems(false);
|
|
result.setNumItems(BigInteger.ZERO);
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL))
|
|
{
|
|
// relationships from and to versions are not preserved
|
|
return result;
|
|
}
|
|
|
|
// get relationships
|
|
List<AssociationRef> assocs = new ArrayList<AssociationRef>();
|
|
if (relationshipDirection == RelationshipDirection.SOURCE
|
|
|| relationshipDirection == RelationshipDirection.EITHER)
|
|
{
|
|
assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
if (relationshipDirection == RelationshipDirection.TARGET
|
|
|| relationshipDirection == RelationshipDirection.EITHER)
|
|
{
|
|
assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL));
|
|
}
|
|
|
|
int skip = (skipCount == null ? 0 : skipCount.intValue());
|
|
int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
|
|
int counter = 0;
|
|
boolean hasMore = false;
|
|
|
|
if (max > 0)
|
|
{
|
|
// filter relationships that not map the CMIS domain model
|
|
for (AssociationRef assocRef : assocs)
|
|
{
|
|
TypeDefinitionWrapper assocTypeDef = getOpenCMISDictionaryService().findAssocType(assocRef.getTypeQName());
|
|
if (assocTypeDef == null || getType(assocRef.getSourceRef()) == null
|
|
|| getType(assocRef.getTargetRef()) == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((typeId != null) && !assocTypeDef.getTypeId().equals(typeId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
counter++;
|
|
|
|
if (skip > 0)
|
|
{
|
|
skip--;
|
|
continue;
|
|
}
|
|
|
|
max--;
|
|
if (max > 0)
|
|
{
|
|
try
|
|
{
|
|
result.getObjects().add(
|
|
createCMISObject(createNodeInfo(assocRef), filter, includeAllowableActions,
|
|
IncludeRelationships.NONE, RENDITION_NONE, false, false/*, cmisVersion*/));
|
|
}
|
|
catch(CmisObjectNotFoundException e)
|
|
{
|
|
// ignore objects that have not been found (perhaps because their type is unknown to CMIS)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hasMore = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
result.setNumItems(BigInteger.valueOf(counter));
|
|
result.setHasMoreItems(hasMore);
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<RenditionData> getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems,
|
|
BigInteger skipCount)
|
|
{
|
|
CMISRenditionMapping mapping = getRenditionMapping();
|
|
return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount);
|
|
}
|
|
|
|
public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions)
|
|
{
|
|
AccessControlListImpl result = new AccessControlListImpl();
|
|
|
|
// get the permissions and sort them
|
|
ArrayList<AccessPermission> ordered = new ArrayList<AccessPermission>(
|
|
permissionService.getAllSetPermissions(nodeRef));
|
|
Collections.sort(ordered, new AccessPermissionComparator());
|
|
|
|
// remove denied permissions and create OpenCMIS objects
|
|
Map<String, Map<Boolean, AccessControlEntryImpl>> aceMap = new HashMap<String, Map<Boolean, AccessControlEntryImpl>>();
|
|
for (AccessPermission entry : ordered)
|
|
{
|
|
if (entry.getAccessStatus() == AccessStatus.ALLOWED)
|
|
{
|
|
// add allowed entries
|
|
Map<Boolean, AccessControlEntryImpl> directAce = aceMap.get(entry.getAuthority());
|
|
if (directAce == null)
|
|
{
|
|
directAce = new HashMap<Boolean, AccessControlEntryImpl>();
|
|
aceMap.put(entry.getAuthority(), directAce);
|
|
}
|
|
|
|
AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly());
|
|
if (ace == null)
|
|
{
|
|
ace = new AccessControlEntryImpl();
|
|
ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority()));
|
|
ace.setPermissions(new ArrayList<String>());
|
|
ace.setDirect(entry.isSetDirectly());
|
|
directAce.put(entry.isSetDirectly(), ace);
|
|
}
|
|
|
|
ace.getPermissions().add(entry.getPermission());
|
|
}
|
|
else if (entry.getAccessStatus() == AccessStatus.DENIED)
|
|
{
|
|
// remove denied entries
|
|
Map<Boolean, AccessControlEntryImpl> directAce = aceMap.get(entry.getAuthority());
|
|
if (directAce != null)
|
|
{
|
|
for (AccessControlEntryImpl ace : directAce.values())
|
|
{
|
|
ace.getPermissions().remove(entry.getPermission());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust permissions, add CMIS permissions and add ACEs to ACL
|
|
List<Ace> aces = new ArrayList<Ace>();
|
|
result.setAces(aces);
|
|
for (Map<Boolean, AccessControlEntryImpl> bothAces : aceMap.values())
|
|
{
|
|
// get, translate and set direct ACE
|
|
AccessControlEntryImpl directAce = bothAces.get(true);
|
|
if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
|
|
{
|
|
List<String> permissions = translatePermissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions);
|
|
if(permissions != null && !permissions.isEmpty())
|
|
{
|
|
// tck doesn't like empty permissions list
|
|
directAce.setPermissions(permissions);
|
|
aces.add(directAce);
|
|
}
|
|
}
|
|
|
|
// get, translate, remove duplicate permissions and set indirect ACE
|
|
AccessControlEntryImpl indirectAce = bothAces.get(false);
|
|
if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty()))
|
|
{
|
|
List<String> permissions = translatePermissionsToCMIS(indirectAce.getPermissions(), onlyBasicPermissions);
|
|
indirectAce.setPermissions(permissions);
|
|
|
|
// remove permissions that are already set in the direct ACE
|
|
if ((directAce != null) && (!directAce.getPermissions().isEmpty()))
|
|
{
|
|
indirectAce.getPermissions().removeAll(directAce.getPermissions());
|
|
}
|
|
|
|
if(!indirectAce.getPermissions().isEmpty())
|
|
{
|
|
// tck doesn't like empty permissions list
|
|
aces.add(indirectAce);
|
|
}
|
|
}
|
|
}
|
|
|
|
result.setExact(!onlyBasicPermissions);
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<String> translatePermissionsToCMIS(List<String> permissions, boolean onlyBasicPermissions)
|
|
{
|
|
Set<String> result = new TreeSet<String>();
|
|
|
|
for (String permission : permissions)
|
|
{
|
|
PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission);
|
|
|
|
if (onlyBasicPermissions)
|
|
{
|
|
|
|
// check for full permissions
|
|
if (permissionModelDao.hasFull(permissionReference))
|
|
{
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
|
|
// check short forms
|
|
Set<PermissionReference> longForms = permissionModelDao.getGranteePermissions(permissionReference);
|
|
|
|
HashSet<String> shortForms = new HashSet<String>();
|
|
for (PermissionReference longForm : longForms)
|
|
{
|
|
shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString());
|
|
}
|
|
|
|
for (String perm : shortForms)
|
|
{
|
|
if (PermissionService.READ.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
}
|
|
else if (PermissionService.WRITE.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.WRITE);
|
|
}
|
|
else if (PermissionService.ALL_PERMISSIONS.equals(perm))
|
|
{
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
}
|
|
|
|
// check the permission
|
|
if (PermissionService.READ.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
}
|
|
else if (PermissionService.WRITE.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.WRITE);
|
|
}
|
|
else if (PermissionService.ALL_PERMISSIONS.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ACE-2224: only repository specific permissions should be reported
|
|
if (permission.startsWith("{"))
|
|
{
|
|
result.add(permission);
|
|
}
|
|
// do revert conversion for basic permissions that have exact matching
|
|
else if (PermissionService.READ.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.READ);
|
|
}
|
|
else if (PermissionService.WRITE.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.WRITE);
|
|
}
|
|
else if (PermissionService.ALL_PERMISSIONS.equals(permission))
|
|
{
|
|
result.add(BasicPermissions.ALL);
|
|
}
|
|
else
|
|
{
|
|
result.add(permissionReference.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
return new ArrayList<String>(result);
|
|
}
|
|
|
|
public static class AccessPermissionComparator implements Comparator<AccessPermission>
|
|
{
|
|
public int compare(AccessPermission left, AccessPermission right)
|
|
{
|
|
if (left.getPosition() != right.getPosition())
|
|
{
|
|
return right.getPosition() - left.getPosition();
|
|
}
|
|
else
|
|
{
|
|
if (left.getAccessStatus() != right.getAccessStatus())
|
|
{
|
|
return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1;
|
|
}
|
|
else
|
|
{
|
|
int compare = left.getAuthority().compareTo(right.getAuthority());
|
|
if (compare != 0)
|
|
{
|
|
return compare;
|
|
}
|
|
else
|
|
{
|
|
return (left.getPermission().compareTo(right.getPermission()));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies the given ACLs.
|
|
*/
|
|
public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces)
|
|
{
|
|
boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty();
|
|
boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty();
|
|
|
|
if (!hasAdd && !hasRemove)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllableAcl())
|
|
{
|
|
throw new CmisConstraintException("Object is not ACL controllable!");
|
|
}
|
|
|
|
// remove permissions
|
|
if (hasRemove)
|
|
{
|
|
Set<AccessPermission> permissions = permissionService.getAllSetPermissions(nodeRef);
|
|
for (Ace ace : removeAces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
|
|
{
|
|
|
|
AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, principalId,
|
|
0);
|
|
if (!permissions.contains(toCheck))
|
|
{
|
|
throw new CmisConstraintException("No matching ACE found to remove!");
|
|
}
|
|
|
|
permissionService.deletePermission(nodeRef, principalId, permission);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add permissions
|
|
if (hasAdd)
|
|
{
|
|
for (Ace ace : addAces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
for (String permission : translatePermissionsFromCMIS(ace.getPermissions()))
|
|
{
|
|
permissionService.setPermission(nodeRef, principalId, permission, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the given ACL.
|
|
*/
|
|
public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces)
|
|
{
|
|
boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty();
|
|
|
|
if (!hasAces && !permissionService.getInheritParentPermissions(nodeRef))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllableAcl())
|
|
{
|
|
throw new CmisConstraintException("Object is not ACL controllable!");
|
|
}
|
|
|
|
// remove all permissions
|
|
permissionService.deletePermissions(nodeRef);
|
|
|
|
// set new permissions
|
|
for (Ace ace : aces.getAces())
|
|
{
|
|
String principalId = ace.getPrincipalId();
|
|
if (CMIS_USER.equals(principalId))
|
|
{
|
|
principalId = AuthenticationUtil.getFullyAuthenticatedUser();
|
|
}
|
|
|
|
List<String> permissions = translatePermissionsFromCMIS(ace.getPermissions());
|
|
for (String permission : permissions)
|
|
{
|
|
permissionService.setPermission(nodeRef, principalId, permission, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<String> translatePermissionsFromCMIS(List<String> permissions)
|
|
{
|
|
List<String> result = new ArrayList<String>();
|
|
|
|
if (permissions == null)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
for (String permission : permissions)
|
|
{
|
|
if (permission == null)
|
|
{
|
|
throw new CmisConstraintException("Invalid null permission!");
|
|
}
|
|
|
|
if (BasicPermissions.READ.equals(permission))
|
|
{
|
|
result.add(PermissionService.READ);
|
|
}
|
|
else if (BasicPermissions.WRITE.equals(permission))
|
|
{
|
|
result.add(PermissionService.WRITE);
|
|
}
|
|
else if (BasicPermissions.ALL.equals(permission))
|
|
{
|
|
result.add(PermissionService.ALL_PERMISSIONS);
|
|
}
|
|
else if (!permission.startsWith("{"))
|
|
{
|
|
result.add(permission);
|
|
}
|
|
else
|
|
{
|
|
int sepIndex = permission.lastIndexOf('.');
|
|
if (sepIndex == -1)
|
|
{
|
|
result.add(permission);
|
|
}
|
|
else
|
|
{
|
|
result.add(permission.substring(sepIndex + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List<String> policies)
|
|
{
|
|
if ((policies == null) || (policies.isEmpty()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!type.getTypeDefinition(false).isControllablePolicy())
|
|
{
|
|
throw new CmisConstraintException("Object is not policy controllable!");
|
|
}
|
|
|
|
// nothing else to do...
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public ObjectList query(String statement, Boolean includeAllowableActions,
|
|
IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount/*, CmisVersion cmisVersion*/)
|
|
{
|
|
// prepare results
|
|
ObjectListImpl result = new ObjectListImpl();
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
// prepare query
|
|
CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef());
|
|
CmisVersion cmisVersion = getRequestCmisVersion();
|
|
options.setCmisVersion(cmisVersion);
|
|
options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS);
|
|
|
|
int skip = 0;
|
|
if ((skipCount != null) && (skipCount.intValue() >= 0))
|
|
{
|
|
skip = skipCount.intValue();
|
|
options.setSkipCount(skip);
|
|
}
|
|
|
|
if ((maxItems != null) && (maxItems.intValue() >= 0))
|
|
{
|
|
options.setMaxItems(maxItems.intValue());
|
|
}
|
|
|
|
boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE)
|
|
|| (!RENDITION_NONE.equals(renditionFilter));
|
|
|
|
// query
|
|
CMISResultSet rs = getOpenCMISQueryService().query(options);
|
|
try
|
|
{
|
|
CMISResultSetColumn[] columns = rs.getMetaData().getColumns();
|
|
|
|
for (CMISResultSetRow row : rs)
|
|
{
|
|
NodeRef nodeRef = row.getNodeRef();
|
|
|
|
if(!nodeService.exists(nodeRef) || filter(nodeRef))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TypeDefinitionWrapper type = getType(nodeRef);
|
|
if (type == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ObjectDataImpl hit = new ObjectDataImpl();
|
|
PropertiesImpl properties = new PropertiesImpl();
|
|
hit.setProperties(properties);
|
|
|
|
Map<String, Serializable> values = row.getValues();
|
|
|
|
for (CMISResultSetColumn column : columns)
|
|
{
|
|
AbstractPropertyData<?> property = getProperty(column.getCMISDataType(),
|
|
column.getCMISPropertyDefinition(), values.get(column.getName()));
|
|
property.setQueryName(column.getName());
|
|
properties.addProperty(property);
|
|
}
|
|
|
|
if (fetchObject)
|
|
{
|
|
// set allowable actions
|
|
if (includeAllowableActions)
|
|
{
|
|
CMISNodeInfoImpl nodeInfo = createNodeInfo(nodeRef);
|
|
if(!nodeInfo.getObjectVariant().equals(CMISObjectVariant.NOT_EXISTING))
|
|
{
|
|
hit.setAllowableActions(getAllowableActions(nodeInfo));
|
|
}
|
|
}
|
|
|
|
// set relationships
|
|
if (includeRelationships != IncludeRelationships.NONE)
|
|
{
|
|
hit.setRelationships(getRelationships(nodeRef, includeRelationships/*, cmisVersion*/));
|
|
}
|
|
|
|
// set renditions
|
|
if (!RENDITION_NONE.equals(renditionFilter))
|
|
{
|
|
List<RenditionData> renditions = getRenditions(nodeRef, renditionFilter, null, null);
|
|
if ((renditions != null) && (!renditions.isEmpty()))
|
|
{
|
|
hit.setRenditions(renditions);
|
|
}
|
|
else
|
|
{
|
|
hit.setRenditions(Collections.EMPTY_LIST);
|
|
}
|
|
}
|
|
}
|
|
|
|
result.getObjects().add(hit);
|
|
}
|
|
|
|
long numberFound = rs.getNumberFound();
|
|
if(numberFound != -1)
|
|
{
|
|
result.setNumItems(BigInteger.valueOf(numberFound));
|
|
}
|
|
result.setHasMoreItems(rs.hasMore());
|
|
|
|
} finally
|
|
{
|
|
rs.close();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets property values.
|
|
*/
|
|
@SuppressWarnings({ "rawtypes" })
|
|
public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude)
|
|
{
|
|
if (properties == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Map<String, PropertyData<?>> incomingPropsMap = properties.getProperties();
|
|
if (incomingPropsMap == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// extract property data into an easier to use form
|
|
Map<String, Pair<TypeDefinitionWrapper, Serializable>> propsMap = new HashMap<String, Pair<TypeDefinitionWrapper, Serializable>>();
|
|
for (String propertyId : incomingPropsMap.keySet())
|
|
{
|
|
PropertyData<?> property = incomingPropsMap.get(propertyId);
|
|
PropertyDefinitionWrapper propDef = type.getPropertyById(property.getId());
|
|
if (propDef == null)
|
|
{
|
|
propDef = getOpenCMISDictionaryService().findProperty(propertyId);
|
|
if (propDef == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!");
|
|
}
|
|
}
|
|
|
|
Updatability updatability = propDef.getPropertyDefinition().getUpdatability();
|
|
if ((updatability == Updatability.READONLY)
|
|
|| (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef)))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!");
|
|
}
|
|
TypeDefinitionWrapper propType = propDef.getOwningType();
|
|
Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI);
|
|
Pair<TypeDefinitionWrapper, Serializable> pair = new Pair<TypeDefinitionWrapper, Serializable>(propType, value);
|
|
propsMap.put(propertyId, pair);
|
|
}
|
|
|
|
// Need to do deal with secondary types first
|
|
Pair<TypeDefinitionWrapper, Serializable> pair = propsMap.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
|
|
Serializable secondaryTypesProperty = (pair != null ? pair.getSecond() : null);
|
|
if(secondaryTypesProperty != null)
|
|
{
|
|
if (!(secondaryTypesProperty instanceof List))
|
|
{
|
|
throw new CmisInvalidArgumentException("Secondary types must be a list!");
|
|
}
|
|
List secondaryTypes = (List)secondaryTypesProperty;
|
|
if(secondaryTypes != null && secondaryTypes.size() > 0)
|
|
{
|
|
// add/remove secondary types/aspects
|
|
processSecondaryTypes(nodeRef, secondaryTypes, propsMap);
|
|
}
|
|
}
|
|
|
|
for (String propertyId : propsMap.keySet())
|
|
{
|
|
if(propertyId.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS))
|
|
{
|
|
// already handled above
|
|
continue;
|
|
}
|
|
|
|
pair = propsMap.get(propertyId);
|
|
TypeDefinitionWrapper propType = pair.getFirst();
|
|
Serializable value = pair.getSecond();
|
|
if (Arrays.binarySearch(exclude, propertyId) < 0)
|
|
{
|
|
setProperty(nodeRef, propType, propertyId, value);
|
|
}
|
|
}
|
|
|
|
List<CmisExtensionElement> extensions = properties.getExtensions();
|
|
if (extensions != null)
|
|
{
|
|
boolean isNameChanging = properties.getProperties().containsKey(PropertyIds.NAME);
|
|
|
|
for (CmisExtensionElement extension : extensions)
|
|
{
|
|
if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())
|
|
&& SET_ASPECTS.equals(extension.getName()))
|
|
{
|
|
setAspectProperties(nodeRef, isNameChanging, extension);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
private void processSecondaryTypes(NodeRef nodeRef, List secondaryTypes, Map<String, Pair<TypeDefinitionWrapper, Serializable>> propsToAdd)
|
|
{
|
|
// diff existing aspects and secondaryTypes/aspects list
|
|
Set<QName> existingAspects = nodeService.getAspects(nodeRef);
|
|
Set<QName> secondaryTypeAspects = new HashSet<QName>();
|
|
for(Object o : secondaryTypes)
|
|
{
|
|
String secondaryType = (String)o;
|
|
|
|
TypeDefinitionWrapper wrapper = getOpenCMISDictionaryService().findType(secondaryType);
|
|
if(wrapper != null)
|
|
{
|
|
QName aspectQName = wrapper.getAlfrescoName();
|
|
secondaryTypeAspects.add(aspectQName);
|
|
}
|
|
else
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid secondary type id " + secondaryType);
|
|
}
|
|
}
|
|
|
|
Set<QName> ignore = new HashSet<QName>();
|
|
ignore.add(ContentModel.ASPECT_REFERENCEABLE);
|
|
ignore.add(ContentModel.ASPECT_LOCALIZED);
|
|
|
|
// aspects to add == the list of secondary types - existing aspects - ignored aspects
|
|
Set<QName> toAdd = new HashSet<QName>(secondaryTypeAspects);
|
|
toAdd.removeAll(existingAspects);
|
|
toAdd.removeAll(ignore);
|
|
|
|
// aspects to remove == existing aspects - secondary types
|
|
Set<QName> aspectsToRemove = new HashSet<QName>();
|
|
aspectsToRemove.addAll(existingAspects);
|
|
aspectsToRemove.removeAll(ignore);
|
|
Iterator<QName> it = aspectsToRemove.iterator();
|
|
while(it.hasNext())
|
|
{
|
|
QName aspectQName = it.next();
|
|
TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
|
|
if(w == null || secondaryTypeAspects.contains(aspectQName))
|
|
{
|
|
// the type is not exposed or is in the secondary types to set, so remove it from the to remove set
|
|
it.remove();
|
|
}
|
|
}
|
|
|
|
// first, remove aspects
|
|
for(QName aspectQName : aspectsToRemove)
|
|
{
|
|
nodeService.removeAspect(nodeRef, aspectQName);
|
|
// aspect is being removed so remove all of its properties from the propsToAdd map
|
|
TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
|
|
for(PropertyDefinitionWrapper wr : w.getProperties())
|
|
{
|
|
String propertyId = wr.getPropertyId();
|
|
propsToAdd.remove(propertyId);
|
|
}
|
|
}
|
|
|
|
// add aspects and properties
|
|
for(QName aspectQName : toAdd)
|
|
{
|
|
nodeService.addAspect(nodeRef, aspectQName, null);
|
|
|
|
// get aspect properties
|
|
AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName);
|
|
Map<QName, org.alfresco.service.cmr.dictionary.PropertyDefinition> aspectPropDefs = aspectDef.getProperties();
|
|
TypeDefinitionWrapper w = getOpenCMISDictionaryService().findNodeType(aspectQName);
|
|
// for each aspect property...
|
|
for(QName propQName : aspectPropDefs.keySet())
|
|
{
|
|
// find CMIS property id
|
|
PropertyDefinitionWrapper property = w.getPropertyByQName(propQName);
|
|
String propertyId = property.getPropertyId();
|
|
if(!propsToAdd.containsKey(propertyId))
|
|
{
|
|
TypeDefinitionWrapper propType = property.getOwningType();
|
|
// CMIS 1.1 secondary types specification requires that all secondary type properties are set
|
|
// property not included in propsToAdd, add it with null value
|
|
Pair<TypeDefinitionWrapper, Serializable> pair = new Pair<TypeDefinitionWrapper, Serializable>(propType, null);
|
|
propsToAdd.put(propertyId, pair);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addSecondaryTypes(NodeRef nodeRef, List<String> secondaryTypes)
|
|
{
|
|
if(secondaryTypes != null && secondaryTypes.size() > 0)
|
|
{
|
|
for(String secondaryType : secondaryTypes)
|
|
{
|
|
TypeDefinitionWrapper type = getType(secondaryType);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType);
|
|
}
|
|
nodeService.addAspect(nodeRef, type.getAlfrescoName(), null);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeSecondaryTypes(NodeRef nodeRef, List<String> secondaryTypes)
|
|
{
|
|
if(secondaryTypes != null && secondaryTypes.size() > 0)
|
|
{
|
|
for(String secondaryType : secondaryTypes)
|
|
{
|
|
TypeDefinitionWrapper type = getType(secondaryType);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid secondaryType: " + secondaryType);
|
|
}
|
|
nodeService.removeAspect(nodeRef, type.getAlfrescoName());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setAspectProperties(NodeRef nodeRef, boolean isNameChanging, CmisExtensionElement aspectExtension)
|
|
{
|
|
if (aspectExtension.getChildren() == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<String> aspectsToAdd = new ArrayList<String>();
|
|
List<String> aspectsToRemove = new ArrayList<String>();
|
|
Map<QName, List<Serializable>> aspectProperties = new HashMap<QName, List<Serializable>>();
|
|
|
|
for (CmisExtensionElement extension : aspectExtension.getChildren())
|
|
{
|
|
if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null))
|
|
{
|
|
aspectsToAdd.add(extension.getValue());
|
|
}
|
|
else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null))
|
|
{
|
|
aspectsToRemove.add(extension.getValue());
|
|
}
|
|
else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null))
|
|
{
|
|
for (CmisExtensionElement property : extension.getChildren())
|
|
{
|
|
if (!property.getName().startsWith("property"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get(
|
|
"propertyDefinitionId"));
|
|
if ((propertyId == null) || (property.getChildren() == null))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PropertyType propertyType = PropertyType.STRING;
|
|
DatatypeFactory df = null;
|
|
if (property.getName().equals("propertyBoolean"))
|
|
{
|
|
propertyType = PropertyType.BOOLEAN;
|
|
}
|
|
else if (property.getName().equals("propertyInteger"))
|
|
{
|
|
propertyType = PropertyType.INTEGER;
|
|
}
|
|
else if (property.getName().equals("propertyDateTime"))
|
|
{
|
|
propertyType = PropertyType.DATETIME;
|
|
try
|
|
{
|
|
df = DatatypeFactory.newInstance();
|
|
}
|
|
catch (DatatypeConfigurationException e)
|
|
{
|
|
throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
else if (property.getName().equals("propertyDecimal"))
|
|
{
|
|
propertyType = PropertyType.DECIMAL;
|
|
}
|
|
|
|
ArrayList<Serializable> values = new ArrayList<Serializable>();
|
|
if (property.getChildren() != null)
|
|
{
|
|
// try
|
|
// {
|
|
for (CmisExtensionElement valueElement : property.getChildren())
|
|
{
|
|
if ("value".equals(valueElement.getName()))
|
|
{
|
|
switch (propertyType)
|
|
{
|
|
case BOOLEAN:
|
|
try
|
|
{
|
|
values.add(Boolean.parseBoolean(valueElement.getValue()));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
|
|
}
|
|
break;
|
|
case DATETIME:
|
|
try
|
|
{
|
|
values.add(df.newXMLGregorianCalendar(valueElement.getValue())
|
|
.toGregorianCalendar());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
|
|
}
|
|
break;
|
|
case INTEGER:
|
|
BigInteger value = null;
|
|
try
|
|
{
|
|
value = new BigInteger(valueElement.getValue());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
|
|
}
|
|
|
|
// overflow check
|
|
PropertyDefinitionWrapper propDef = getOpenCMISDictionaryService().findProperty(propertyId);
|
|
if(propDef == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!");
|
|
}
|
|
|
|
QName propertyQName = propDef.getPropertyAccessor().getMappedProperty();
|
|
if (propertyQName == null)
|
|
{
|
|
throw new CmisConstraintException("Unable to set property " + propertyId + "!");
|
|
}
|
|
|
|
org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName);
|
|
QName dataDef = def.getDataType().getName();
|
|
|
|
if (dataDef.equals(DataTypeDefinition.INT) && (value.compareTo(maxInt) > 0 || value.compareTo(minInt) < 0))
|
|
{
|
|
throw new CmisConstraintException("Value is out of range for property " + propertyId);
|
|
}
|
|
|
|
if (dataDef.equals(DataTypeDefinition.LONG) && (value.compareTo(maxLong) > 0 || value.compareTo(minLong) < 0 ))
|
|
{
|
|
throw new CmisConstraintException("Value is out of range for property " + propertyId);
|
|
}
|
|
|
|
values.add(value);
|
|
break;
|
|
case DECIMAL:
|
|
try
|
|
{
|
|
values.add(new BigDecimal(valueElement.getValue()));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e);
|
|
}
|
|
break;
|
|
default:
|
|
values.add(valueElement.getValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
aspectProperties.put(QName.createQName(propertyId, namespaceService), values);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove and add aspects
|
|
String aspectType = null;
|
|
try
|
|
{
|
|
for (String aspect : aspectsToRemove)
|
|
{
|
|
aspectType = aspect;
|
|
|
|
TypeDefinitionWrapper type = getType(aspect);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
|
|
QName typeName = type.getAlfrescoName();
|
|
// if aspect is hidden aspect, remove only if hidden node is not client controlled
|
|
if(typeName.equals(ContentModel.ASPECT_HIDDEN))
|
|
{
|
|
if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
{
|
|
// manipulate hidden aspect only if client controlled
|
|
nodeService.removeAspect(nodeRef, typeName);
|
|
}
|
|
|
|
|
|
|
|
// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
// {
|
|
// nodeService.removeAspect(nodeRef, typeName);
|
|
// }
|
|
}
|
|
else
|
|
{
|
|
nodeService.removeAspect(nodeRef, typeName);
|
|
}
|
|
}
|
|
|
|
for (String aspect : aspectsToAdd)
|
|
{
|
|
aspectType = aspect;
|
|
|
|
TypeDefinitionWrapper type = getType(aspect);
|
|
if (type == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
|
|
QName typeName = type.getAlfrescoName();
|
|
// if aspect is hidden aspect, remove only if hidden node is not client controlled
|
|
if(typeName.equals(ContentModel.ASPECT_HIDDEN))
|
|
{
|
|
if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
{
|
|
// manipulate hidden aspect only if client controlled
|
|
nodeService.addAspect(nodeRef, type.getAlfrescoName(),
|
|
Collections.<QName, Serializable> emptyMap());
|
|
}
|
|
|
|
// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
// {
|
|
// nodeService.addAspect(nodeRef, type.getAlfrescoName(),
|
|
// Collections.<QName, Serializable> emptyMap());
|
|
// }
|
|
}
|
|
else
|
|
{
|
|
nodeService.addAspect(nodeRef, type.getAlfrescoName(),
|
|
Collections.<QName, Serializable> emptyMap());
|
|
}
|
|
}
|
|
}
|
|
catch (InvalidAspectException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType);
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid node: " + nodeRef);
|
|
}
|
|
|
|
// set property
|
|
for (Map.Entry<QName, List<Serializable>> property : aspectProperties.entrySet())
|
|
{
|
|
QName propertyQName = property.getKey();
|
|
|
|
if (property.getValue().isEmpty())
|
|
{
|
|
if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey()))
|
|
{
|
|
if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
{
|
|
// manipulate hidden aspect property only if client controlled
|
|
nodeService.removeProperty(nodeRef, propertyQName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nodeService.removeProperty(nodeRef, property.getKey());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey()))
|
|
{
|
|
if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED))
|
|
{
|
|
// manipulate hidden aspect property only if client controlled
|
|
nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property
|
|
.getValue().get(0) : (Serializable) property.getValue());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Serializable value = (Serializable)property.getValue();
|
|
nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property
|
|
.getValue().get(0) : value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a property value.
|
|
*/
|
|
public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, String propertyId, Serializable value)
|
|
{
|
|
if (propertyId == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Cannot process not null property!");
|
|
}
|
|
|
|
PropertyDefinitionWrapper propDef = type.getPropertyById(propertyId);
|
|
if (propDef == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + propertyId + " is unknown!");
|
|
}
|
|
|
|
Updatability updatability = propDef.getPropertyDefinition().getUpdatability();
|
|
if ((updatability == Updatability.READONLY)
|
|
|| (updatability == Updatability.WHENCHECKEDOUT && !checkOutCheckInService.isWorkingCopy(nodeRef)))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + propertyId + " is read-only!");
|
|
}
|
|
|
|
if(propDef.getPropertyId().equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS))
|
|
{
|
|
throw new IllegalArgumentException("Cannot process " + PropertyIds.SECONDARY_OBJECT_TYPE_IDS + " in setProperty");
|
|
}
|
|
else
|
|
{
|
|
QName propertyQName = propDef.getPropertyAccessor().getMappedProperty();
|
|
if (propertyQName == null)
|
|
{
|
|
throw new CmisConstraintException("Unable to set property " + propertyId + "!");
|
|
}
|
|
|
|
if (propertyId.equals(PropertyIds.NAME))
|
|
{
|
|
if (!(value instanceof String))
|
|
{
|
|
throw new CmisInvalidArgumentException("Object name must be a string!");
|
|
}
|
|
|
|
try
|
|
{
|
|
fileFolderService.rename(nodeRef, value.toString());
|
|
}
|
|
catch (FileExistsException e)
|
|
{
|
|
throw new CmisContentAlreadyExistsException("An object with this name already exists!", e);
|
|
}
|
|
catch (FileNotFoundException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Object with id " + nodeRef.getId() + " not found!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// overflow check
|
|
if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.INTEGER && value instanceof BigInteger)
|
|
{
|
|
org.alfresco.service.cmr.dictionary.PropertyDefinition def = dictionaryService.getProperty(propertyQName);
|
|
QName dataDef = def.getDataType().getName();
|
|
BigInteger bigValue = (BigInteger) value;
|
|
|
|
if ((bigValue.compareTo(maxInt) > 0 || bigValue.compareTo(minInt) < 0) && dataDef.equals(DataTypeDefinition.INT))
|
|
{
|
|
throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName());
|
|
}
|
|
|
|
if ((bigValue.compareTo(maxLong) > 0 || bigValue.compareTo(minLong) < 0) && dataDef.equals(DataTypeDefinition.LONG))
|
|
{
|
|
throw new CmisConstraintException("Value is out of range for property " + propertyQName.getLocalName());
|
|
}
|
|
}
|
|
|
|
nodeService.setProperty(nodeRef, propertyQName, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Serializable getValue(PropertyData<?> property, boolean isMultiValue)
|
|
{
|
|
if ((property.getValues() == null) || (property.getValues().isEmpty()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (isMultiValue)
|
|
{
|
|
return (Serializable) property.getValues();
|
|
}
|
|
|
|
return (Serializable) property.getValues().get(0);
|
|
}
|
|
|
|
/**
|
|
* Returns content changes.
|
|
*/
|
|
public ObjectList getContentChanges(Holder<String> changeLogToken, BigInteger maxItems)
|
|
{
|
|
final ObjectListImpl result = new ObjectListImpl();
|
|
result.setObjects(new ArrayList<ObjectData>());
|
|
|
|
EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true)
|
|
{
|
|
@Override
|
|
public boolean handleAuditEntry(Long entryId, String user, long time, Map<String, Serializable> values)
|
|
{
|
|
result.getObjects().addAll(createChangeEvents(time, values));
|
|
return super.handleAuditEntry(entryId, user, time, values);
|
|
}
|
|
};
|
|
|
|
Long from = null;
|
|
if ((changeLogToken != null) && (changeLogToken.getValue() != null))
|
|
{
|
|
try
|
|
{
|
|
from = Long.parseLong(changeLogToken.getValue());
|
|
}
|
|
catch (NumberFormatException e)
|
|
{
|
|
throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken);
|
|
}
|
|
}
|
|
|
|
AuditQueryParameters params = new AuditQueryParameters();
|
|
params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
params.setForward(true);
|
|
params.setFromId(from);
|
|
|
|
int maxResults = (maxItems == null ? 0 : maxItems.intValue());
|
|
maxResults = (maxResults < 1 ? 0 : maxResults + 1);
|
|
|
|
auditService.auditQuery(changeLogCollectingCallback, params, maxResults);
|
|
|
|
String newChangeLogToken = null;
|
|
if (maxResults > 0)
|
|
{
|
|
if (result.getObjects().size() >= maxResults)
|
|
{
|
|
StringBuilder clt = new StringBuilder();
|
|
newChangeLogToken = (from == null ? clt.append(maxItems.intValue() + 1).toString() : clt.append(from.longValue() + maxItems.intValue()).toString());
|
|
result.getObjects().remove(result.getObjects().size() - 1).getId();
|
|
result.setHasMoreItems(true);
|
|
}
|
|
else
|
|
{
|
|
result.setHasMoreItems(false);
|
|
}
|
|
}
|
|
|
|
if (changeLogToken != null)
|
|
{
|
|
changeLogToken.setValue(newChangeLogToken);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private List<ObjectData> createChangeEvents(long time, Map<String, Serializable> values)
|
|
{
|
|
List<ObjectData> result = new ArrayList<ObjectData>();
|
|
|
|
if ((values == null) || (values.size() == 0))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
GregorianCalendar changeTime = new GregorianCalendar();
|
|
changeTime.setTimeInMillis(time);
|
|
|
|
String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/";
|
|
|
|
for (Entry<String, Serializable> entry : values.entrySet())
|
|
{
|
|
if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String path = entry.getKey();
|
|
if (!path.startsWith(appPath))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ChangeType changeType = null;
|
|
String changePath = path.substring(appPath.length()).toLowerCase();
|
|
for (ChangeType c : ChangeType.values())
|
|
{
|
|
if (changePath.startsWith(c.value().toLowerCase()))
|
|
{
|
|
changeType = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (changeType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Map<String, Serializable> valueMap = (Map<String, Serializable>) entry.getValue();
|
|
String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID);
|
|
|
|
// build object
|
|
ObjectDataImpl object = new ObjectDataImpl();
|
|
result.add(object);
|
|
|
|
PropertiesImpl properties = new PropertiesImpl();
|
|
object.setProperties(properties);
|
|
PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId);
|
|
properties.addProperty(objectIdProperty);
|
|
|
|
ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl();
|
|
object.setChangeEventInfo(changeEvent);
|
|
changeEvent.setChangeType(changeType);
|
|
changeEvent.setChangeTime(changeTime);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private class EntryIdCallback implements AuditQueryCallback
|
|
{
|
|
private final boolean valuesRequired;
|
|
private Long entryId;
|
|
|
|
public EntryIdCallback(boolean valuesRequired)
|
|
{
|
|
this.valuesRequired = valuesRequired;
|
|
}
|
|
|
|
public String getEntryId()
|
|
{
|
|
return entryId == null ? null : entryId.toString();
|
|
}
|
|
|
|
public boolean valuesRequired()
|
|
{
|
|
return this.valuesRequired;
|
|
}
|
|
|
|
public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time,
|
|
Map<String, Serializable> values)
|
|
{
|
|
if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION))
|
|
{
|
|
return handleAuditEntry(entryId, user, time, values);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public boolean handleAuditEntry(Long entryId, String user, long time, Map<String, Serializable> values)
|
|
{
|
|
this.entryId = entryId;
|
|
return true;
|
|
}
|
|
|
|
public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error)
|
|
{
|
|
throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error);
|
|
}
|
|
};
|
|
|
|
// --------------------------------------------------------------
|
|
// OpenCMIS methods
|
|
// --------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the value of the given property if it exists and is of the
|
|
* correct type.
|
|
*/
|
|
public String getStringProperty(Properties properties, String propertyId)
|
|
{
|
|
if ((properties == null) || (properties.getProperties() == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
PropertyData<?> property = properties.getProperties().get(propertyId);
|
|
if (!(property instanceof PropertyString))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((PropertyString) property).getFirstValue();
|
|
}
|
|
|
|
/**
|
|
* Returns the value of the given property if it exists and is of the
|
|
* correct type.
|
|
*/
|
|
public String getIdProperty(Properties properties, String propertyId)
|
|
{
|
|
if ((properties == null) || (properties.getProperties() == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
PropertyData<?> property = properties.getProperties().get(propertyId);
|
|
if (!(property instanceof PropertyId))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return ((PropertyId) property).getFirstValue();
|
|
}
|
|
|
|
public String getNameProperty(Properties properties, String fallback)
|
|
{
|
|
String name = getStringProperty(properties, PropertyIds.NAME);
|
|
if ((name == null) || (name.trim().length() == 0))
|
|
{
|
|
if (fallback == null)
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!");
|
|
}
|
|
else
|
|
{
|
|
name = fallback;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
public String getObjectTypeIdProperty(Properties properties)
|
|
{
|
|
String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID);
|
|
if ((objectTypeId == null) || (objectTypeId.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!");
|
|
}
|
|
|
|
return objectTypeId;
|
|
}
|
|
|
|
public String getSourceIdProperty(Properties properties)
|
|
{
|
|
String id = getIdProperty(properties, PropertyIds.SOURCE_ID);
|
|
if ((id == null) || (id.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!");
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
public String getTargetIdProperty(Properties properties)
|
|
{
|
|
String id = getIdProperty(properties, PropertyIds.TARGET_ID);
|
|
if ((id == null) || (id.trim().length() == 0))
|
|
{
|
|
throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!");
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Returns the repository info object.
|
|
*/
|
|
public RepositoryInfo getRepositoryInfo(CmisVersion cmisVersion)
|
|
{
|
|
return createRepositoryInfo(cmisVersion);
|
|
}
|
|
|
|
/**
|
|
* Returns the repository id.
|
|
*/
|
|
public String getRepositoryId()
|
|
{
|
|
return descriptorService.getCurrentRepositoryDescriptor().getId();
|
|
}
|
|
|
|
/**
|
|
* Creates the repository info object.
|
|
*/
|
|
private RepositoryInfo createRepositoryInfo(CmisVersion cmisVersion)
|
|
{
|
|
Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor();
|
|
|
|
// get change token
|
|
boolean auditEnabled = auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/"
|
|
+ CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
String latestChangeLogToken = null;
|
|
|
|
if (auditEnabled)
|
|
{
|
|
EntryIdCallback auditQueryCallback = new EntryIdCallback(false);
|
|
AuditQueryParameters params = new AuditQueryParameters();
|
|
params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION);
|
|
params.setForward(false);
|
|
auditService.auditQuery(auditQueryCallback, params, 1);
|
|
latestChangeLogToken = auditQueryCallback.getEntryId();
|
|
}
|
|
|
|
// compile repository info
|
|
RepositoryInfoImpl ri = new RepositoryInfoImpl();
|
|
|
|
ri.setId(currentDescriptor.getId());
|
|
ri.setName(currentDescriptor.getName());
|
|
ri.setDescription(currentDescriptor.getName());
|
|
ri.setVendorName("Alfresco");
|
|
ri.setProductName("Alfresco " + descriptorService.getServerDescriptor().getEdition());
|
|
ri.setProductVersion(currentDescriptor.getVersion());
|
|
NodeRef rootNodeRef = getRootNodeRef();
|
|
ri.setRootFolder(constructObjectId(rootNodeRef, null));
|
|
ri.setCmisVersion(cmisVersion);
|
|
|
|
ri.setChangesIncomplete(true);
|
|
ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER }));
|
|
ri.setLatestChangeLogToken(latestChangeLogToken);
|
|
ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName());
|
|
ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES);
|
|
|
|
RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl();
|
|
ri.setCapabilities(repCap);
|
|
|
|
repCap.setAllVersionsSearchable(false);
|
|
repCap.setCapabilityAcl(CapabilityAcl.MANAGE);
|
|
repCap.setCapabilityChanges(auditEnabled ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE);
|
|
repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
|
|
repCap.setCapabilityJoin(CapabilityJoin.NONE);
|
|
repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
|
|
repCap.setCapabilityRendition(CapabilityRenditions.READ);
|
|
repCap.setIsPwcSearchable(false);
|
|
repCap.setIsPwcUpdatable(true);
|
|
repCap.setSupportsGetDescendants(true);
|
|
repCap.setSupportsGetFolderTree(true);
|
|
repCap.setSupportsMultifiling(true);
|
|
repCap.setSupportsUnfiling(false);
|
|
repCap.setSupportsVersionSpecificFiling(false);
|
|
|
|
AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl();
|
|
ri.setAclCapabilities(aclCap);
|
|
|
|
aclCap.setAclPropagation(AclPropagation.PROPAGATE);
|
|
aclCap.setSupportedPermissions(SupportedPermissions.BOTH);
|
|
aclCap.setPermissionDefinitionData(repositoryPermissions);
|
|
aclCap.setPermissionMappingData(permissionMappings);
|
|
|
|
return ri;
|
|
}
|
|
|
|
private List<PermissionDefinition> getRepositoryPermissions()
|
|
{
|
|
ArrayList<PermissionDefinition> result = new ArrayList<PermissionDefinition>();
|
|
|
|
Set<PermissionReference> all = permissionModelDao.getAllExposedPermissions();
|
|
for (PermissionReference pr : all)
|
|
{
|
|
result.add(createPermissionDefinition(pr));
|
|
}
|
|
|
|
PermissionReference allPermission = permissionModelDao.getPermissionReference(null,
|
|
PermissionService.ALL_PERMISSIONS);
|
|
result.add(createPermissionDefinition(allPermission));
|
|
|
|
PermissionDefinitionDataImpl cmisPermission;
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setId(BasicPermissions.READ);
|
|
cmisPermission.setDescription("CMIS Read");
|
|
result.add(cmisPermission);
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setId(BasicPermissions.WRITE);
|
|
cmisPermission.setDescription("CMIS Write");
|
|
result.add(cmisPermission);
|
|
|
|
cmisPermission = new PermissionDefinitionDataImpl();
|
|
cmisPermission.setId(BasicPermissions.ALL);
|
|
cmisPermission.setDescription("CMIS All");
|
|
result.add(cmisPermission);
|
|
|
|
return result;
|
|
}
|
|
|
|
private PermissionDefinition createPermissionDefinition(PermissionReference pr)
|
|
{
|
|
PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl();
|
|
permission.setId(pr.getQName().toString() + "." + pr.getName());
|
|
permission.setDescription(permission.getId());
|
|
|
|
return permission;
|
|
}
|
|
|
|
private Map<String, PermissionMapping> getPermissionMappings()
|
|
{
|
|
Map<String, PermissionMapping> result = new HashMap<String, PermissionMapping>();
|
|
|
|
for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class))
|
|
{
|
|
for (Map.Entry<String, List<String>> m : e.getPermissionMapping().entrySet())
|
|
{
|
|
PermissionMappingDataImpl mapping = new PermissionMappingDataImpl();
|
|
mapping.setKey(m.getKey());
|
|
mapping.setPermissions(m.getValue());
|
|
|
|
result.put(mapping.getKey(), mapping);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private CMISRenditionMapping getRenditionMapping()
|
|
{
|
|
CMISRenditionMapping renditionMapping = (CMISRenditionMapping)singletonCache.get(KEY_CMIS_RENDITION_MAPPING_NODEREF);
|
|
if (renditionMapping == null)
|
|
{
|
|
renditionMapping = new CMISRenditionMapping(nodeService, contentService, renditionService,
|
|
transactionService, kindToRenditionNames);
|
|
|
|
singletonCache.put(KEY_CMIS_RENDITION_MAPPING_NODEREF, renditionMapping);
|
|
}
|
|
return renditionMapping;
|
|
}
|
|
}
|