filterProps = null;
Query q = parameters.getQuery();
if (q != null)
{
MapBasedQueryWalker propertyWalker = createListChildrenQueryWalker();
QueryHelper.walk(q, propertyWalker);
Boolean isPrimary = propertyWalker.getProperty(PARAM_ISPRIMARY, WhereClauseParser.EQUALS, Boolean.class);
if (isPrimary != null)
{
filterProps = new ArrayList<>(1);
filterProps.add(new FilterPropBoolean(GetChildrenCannedQuery.FILTER_QNAME_NODE_IS_PRIMARY, isPrimary));
}
}
return filterProps;
}
/**
* Returns a List of sort properties specified by the "sorting" request parameter.
*
* @param parameters The {@link Parameters} object to get the parameters passed into the request
* including:
* - filter, sort & paging params (where, orderBy, skipCount, maxItems)
* - incFiles, incFolders (both true by default)
* @return The list of Pair<QName, Boolean>
sort properties. If no sort parameters are
* found defaults to {@link #getListChildrenSortPropsDefault() getListChildrenSortPropsDefault}.
*/
protected List> getListChildrenSortProps(final Parameters parameters)
{
List sortCols = parameters.getSorting();
List> sortProps;
if ((sortCols != null) && (sortCols.size() > 0))
{
// TODO should we allow isFile in sort (and map to reverse of isFolder) ?
sortProps = new ArrayList<>(sortCols.size());
for (SortColumn sortCol : sortCols)
{
QName propQname = PARAM_SYNONYMS_QNAME.get(sortCol.column);
if (propQname == null)
{
propQname = createQName(sortCol.column);
}
if (propQname != null)
{
sortProps.add(new Pair<>(propQname, sortCol.asc));
}
}
}
else
{
sortProps = getListChildrenSortPropsDefault();
}
return sortProps;
}
/**
*
* Returns the default sort order.
*
*
* @return The list of Pair<QName, Boolean>
sort
* properties.
*/
protected List> getListChildrenSortPropsDefault()
{
List> sortProps = new ArrayList<>(
Arrays.asList(new Pair<>(GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER, Boolean.FALSE), new Pair<>(ContentModel.PROP_NAME, true)));
return sortProps;
}
private Pair parseNodeTypeFilter(String nodeTypeStr)
{
boolean filterIncludeSubTypes = false; // default nodeType filtering is without subTypes (unless nodeType value is suffixed with ' INCLUDESUBTYPES')
int idx = nodeTypeStr.lastIndexOf(' ');
if (idx > 0)
{
String suffix = nodeTypeStr.substring(idx);
if (suffix.equalsIgnoreCase(" "+PARAM_INCLUDE_SUBTYPES))
{
filterIncludeSubTypes = true;
nodeTypeStr = nodeTypeStr.substring(0, idx);
}
}
QName filterNodeTypeQName = createQName(nodeTypeStr);
if (dictionaryService.getType(filterNodeTypeQName) == null)
{
throw new InvalidArgumentException("Unknown filter nodeType: "+nodeTypeStr);
}
return new Pair<>(filterNodeTypeQName, filterIncludeSubTypes);
}
protected Set buildAssocTypes(QName assocTypeQName)
{
Set assocTypeQNames = null;
if (assocTypeQName != null)
{
assocTypeQNames = Collections.singleton(assocTypeQName);
}
/*
// TODO review - this works, but reduces from ~100 to ~96 (OOTB)
// maybe we could post filter (rather than join) - examples: sys:children, sys:lost_found, sys:archivedLink, sys:archiveUserLink
else
{
Collection qnames = dictionaryService.getAllAssociations();
assocTypeQNames = new HashSet<>(qnames.size());
// remove system assoc types
for (QName qname : qnames)
{
if ((!EXCLUDED_NS.contains(qname.getNamespaceURI())))
{
assocTypeQNames.add(qname);
}
}
}
*/
return assocTypeQNames;
}
protected Pair, Set> buildSearchTypesAndIgnoreAspects(QName nodeTypeQName, boolean includeSubTypes, Set ignoreQNameTypes, Boolean includeFiles, Boolean includeFolders)
{
Set searchTypeQNames = new HashSet<>(100);
Set ignoreAspectQNames = null;
if (nodeTypeQName != null)
{
// Build a list of (sub-)types
if (includeSubTypes)
{
Collection qnames = dictionaryService.getSubTypes(nodeTypeQName, true);
searchTypeQNames.addAll(qnames);
}
searchTypeQNames.add(nodeTypeQName);
// Remove 'system' folders
if (includeSubTypes)
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true);
searchTypeQNames.removeAll(qnames);
}
searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER);
}
if (includeFiles != null)
{
if (includeFiles)
{
if (includeSubTypes)
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true);
searchTypeQNames.addAll(qnames);
}
searchTypeQNames.add(ContentModel.TYPE_CONTENT);
}
else
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true);
searchTypeQNames.removeAll(qnames);
searchTypeQNames.remove(ContentModel.TYPE_CONTENT);
}
}
if (includeFolders != null)
{
if (includeFolders)
{
if (includeSubTypes)
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true);
searchTypeQNames.addAll(qnames);
}
searchTypeQNames.add(ContentModel.TYPE_FOLDER);
// Remove 'system' folders
if (includeSubTypes)
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true);
searchTypeQNames.removeAll(qnames);
}
searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER);
}
else
{
Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true);
searchTypeQNames.removeAll(qnames);
searchTypeQNames.remove(ContentModel.TYPE_FOLDER);
}
}
if (ignoreQNameTypes != null)
{
Set ignoreQNamesNotSearchTypes = new HashSet<>(ignoreQNameTypes);
ignoreQNamesNotSearchTypes.removeAll(searchTypeQNames);
ignoreQNamesNotSearchTypes.remove(ContentModel.TYPE_SYSTEM_FOLDER);
if (ignoreQNamesNotSearchTypes.size() > 0)
{
ignoreAspectQNames = getAspectsToIgnore(ignoreQNamesNotSearchTypes);
}
searchTypeQNames.removeAll(ignoreQNameTypes);
}
return new Pair<>(searchTypeQNames, ignoreAspectQNames);
}
protected Pair, Set> buildSearchTypesAndIgnoreAspects(final Parameters parameters)
{
// filters
Boolean includeFolders = null;
Boolean includeFiles = null;
QName filterNodeTypeQName = null;
// note: for files/folders, include subtypes by default (unless filtering by a specific nodeType - see below)
boolean filterIncludeSubTypes = true;
Query q = parameters.getQuery();
if (q != null)
{
// filtering via "where" clause
MapBasedQueryWalker propertyWalker = createListChildrenQueryWalker();
QueryHelper.walk(q, propertyWalker);
Boolean isFolder = propertyWalker.getProperty(PARAM_ISFOLDER, WhereClauseParser.EQUALS, Boolean.class);
Boolean isFile = propertyWalker.getProperty(PARAM_ISFILE, WhereClauseParser.EQUALS, Boolean.class);
if (isFolder != null)
{
includeFolders = isFolder;
}
if (isFile != null)
{
includeFiles = isFile;
}
if (Boolean.TRUE.equals(includeFiles) && Boolean.TRUE.equals(includeFolders))
{
throw new InvalidArgumentException("Invalid filter (isFile=true and isFolder=true) - a node cannot be both a file and a folder");
}
String nodeTypeStr = propertyWalker.getProperty(PARAM_NODETYPE, WhereClauseParser.EQUALS, String.class);
if ((nodeTypeStr != null) && (! nodeTypeStr.isEmpty()))
{
if ((isFile != null) || (isFolder != null))
{
throw new InvalidArgumentException("Invalid filter - nodeType and isFile/isFolder are mutually exclusive");
}
Pair pair = parseNodeTypeFilter(nodeTypeStr);
filterNodeTypeQName = pair.getFirst();
filterIncludeSubTypes = pair.getSecond();
}
}
// notes (see also earlier validation checks):
// - no filtering means any types/sub-types (well, apart from hidden &/or default ignored types - eg. systemfolder, fm types)
// - node type filtering is mutually exclusive from isFile/isFolder, can optionally also include sub-types
// - isFile & isFolder cannot both be true
// - (isFile=false) means any other types/sub-types (other than files)
// - (isFolder=false) means any other types/sub-types (other than folders)
// - (isFile=false and isFolder=false) means any other types/sub-types (other than files or folders)
if (filterNodeTypeQName == null)
{
if ((includeFiles == null) && (includeFolders == null))
{
// no additional filtering
filterNodeTypeQName = ContentModel.TYPE_CMOBJECT;
}
else if ((includeFiles != null) && (includeFolders != null))
{
if ((! includeFiles) && (! includeFolders))
{
// no files or folders
filterNodeTypeQName = ContentModel.TYPE_CMOBJECT;
}
}
else if ((includeFiles != null) && (! includeFiles))
{
// no files
filterNodeTypeQName = ContentModel.TYPE_CMOBJECT;
}
else if ((includeFolders != null) && (! includeFolders))
{
// no folders
filterNodeTypeQName = ContentModel.TYPE_CMOBJECT;
}
}
return buildSearchTypesAndIgnoreAspects(filterNodeTypeQName, filterIncludeSubTypes, ignoreQNames, includeFiles, includeFolders);
}
private Set getAspectsToIgnore(Set ignoreQNames)
{
Set ignoreQNameAspects = new HashSet<>(ignoreQNames.size());
for (QName qname : ignoreQNames)
{
if (dictionaryService.getAspect(qname) != null)
{
ignoreQNameAspects.add(qname);
}
}
return ignoreQNameAspects;
}
@Override
public void deleteNode(String nodeId, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
throw new PermissionDeniedException("Cannot delete: " + nodeId);
}
// default false (if not provided)
boolean permanentDelete = Boolean.valueOf(parameters.getParameter(PARAM_PERMANENT));
if (permanentDelete == true)
{
boolean isAdmin = authorityService.hasAdminAuthority();
if (! isAdmin)
{
String owner = ownableService.getOwner(nodeRef);
if (! AuthenticationUtil.getRunAsUser().equals(owner))
{
// non-owner/non-admin cannot permanently delete (even if they have delete permission)
throw new PermissionDeniedException("Non-owner/non-admin cannot permanently delete: " + nodeId);
}
}
// Set as temporary to delete node instead of archiving.
nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);
}
final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
postActivity(Activity_Type.DELETED, activityInfo, true);
fileFolderService.delete(nodeRef);
}
@Override
public Node createNode(String parentFolderNodeId, Node nodeInfo, Parameters parameters)
{
if (nodeInfo.getNodeRef() != null)
{
throw new InvalidArgumentException("Unexpected id when trying to create a new node: "+nodeInfo.getNodeRef().getId());
}
// check that requested parent node exists and it's type is a (sub-)type of folder
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null);
// node name - mandatory
String nodeName = nodeInfo.getName();
if ((nodeName == null) || nodeName.isEmpty())
{
throw new InvalidArgumentException("Node name is expected: "+parentNodeRef.getId());
}
// node type - check that requested type is a (sub-) type of cm:object
String nodeType = nodeInfo.getNodeType();
if ((nodeType == null) || nodeType.isEmpty())
{
throw new InvalidArgumentException("Node type is expected: "+parentNodeRef.getId()+","+nodeName);
}
QName nodeTypeQName = createQName(nodeType);
boolean isContent = isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT);
if (! isContent)
{
validateCmObject(nodeTypeQName);
}
/* RA-834: commented-out since not currently applicable for empty file
List thumbnailDefs = null;
String renditionsParam = parameters.getParameter(PARAM_RENDITIONS);
if (renditionsParam != null)
{
if (!isContent)
{
throw new InvalidArgumentException("Renditions ['"+renditionsParam+"'] only apply to content types: "+parentNodeRef.getId()+","+nodeName);
}
thumbnailDefs = getThumbnailDefs(renditionsParam);
}
*/
Map props = new HashMap<>(1);
if (nodeInfo.getProperties() != null)
{
// node properties - set any additional properties
props = mapToNodeProperties(nodeInfo.getProperties());
}
// Optionally, lookup by relative path
String relativePath = nodeInfo.getRelativePath();
parentNodeRef = getOrCreatePath(parentNodeRef, relativePath);
// Existing file/folder name handling
boolean autoRename = Boolean.valueOf(parameters.getParameter(PARAM_AUTO_RENAME));
if (autoRename && (isContent || isSubClass(nodeTypeQName, ContentModel.TYPE_FOLDER)))
{
NodeRef existingNode = nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, nodeName);
if (existingNode != null)
{
// File already exists, find a unique name
nodeName = findUniqueName(parentNodeRef, nodeName);
}
}
QName assocTypeQName = ContentModel.ASSOC_CONTAINS;
if ((nodeInfo.getAssociation() != null) && (nodeInfo.getAssociation().getAssocType() != null))
{
assocTypeQName = getAssocType(nodeInfo.getAssociation().getAssocType());
}
Boolean versionMajor = null;
String str = parameters.getParameter(PARAM_VERSION_MAJOR);
if (str != null)
{
versionMajor = new Boolean(str);
}
String versionComment = parameters.getParameter(PARAM_VERSION_COMMENT);
// Create the node
NodeRef nodeRef;
if (isContent)
{
// create empty file node - note: currently will be set to default encoding only (UTF-8)
nodeRef = createNewFile(parentNodeRef, nodeName, nodeTypeQName, null, props, assocTypeQName, parameters, versionMajor, versionComment);
}
else
{
// create non-content node
nodeRef = createNodeImpl(parentNodeRef, nodeName, nodeTypeQName, props, assocTypeQName);
}
addCustomAspects(nodeRef, nodeInfo.getAspectNames(), EXCLUDED_ASPECTS);
// eg. to create mandatory assoc(s)
if (nodeInfo.getTargets() != null)
{
addTargets(nodeRef.getId(), nodeInfo.getTargets());
}
if (nodeInfo.getSecondaryChildren() != null)
{
addChildren(nodeRef.getId(), nodeInfo.getSecondaryChildren());
}
Node newNode = getFolderOrDocument(nodeRef.getId(), parameters);
/* RA-834: commented-out since not currently applicable for empty file
requestRenditions(thumbnailDefs, newNode); // note: noop for folder
*/
return newNode;
}
public void addCustomAspects(NodeRef nodeRef, List aspectNames, List excludedAspects)
{
if (aspectNames == null)
{
return;
}
// node aspects - set any additional aspects
Set aspectQNames = mapToNodeAspects(aspectNames);
for (QName aspectQName : aspectQNames)
{
if (excludedAspects.contains(aspectQName) || aspectQName.equals(ContentModel.ASPECT_AUDITABLE))
{
continue; // ignore
}
nodeService.addAspect(nodeRef, aspectQName, null);
}
}
private NodeRef getOrCreatePath(NodeRef parentNodeRef, String relativePath)
{
if (relativePath != null)
{
List pathElements = getPathElements(relativePath);
// Checks for the presence of, and creates as necessary,
// the folder structure in the provided path elements list.
if (!pathElements.isEmpty())
{
parentNodeRef = makeFolders(parentNodeRef, pathElements);
}
}
return parentNodeRef;
}
public List addChildren(String parentNodeId, List entities)
{
NodeRef parentNodeRef = validateNode(parentNodeId);
List result = new ArrayList<>(entities.size());
for (AssocChild assoc : entities)
{
String childId = assoc.getChildId();
if (childId == null)
{
throw new InvalidArgumentException("Missing childId");
}
QName assocTypeQName = getAssocType(assoc.getAssocType());
try
{
NodeRef childNodeRef = validateNode(childId);
String nodeName = (String)nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME);
QName assocChildQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(nodeName));
nodeService.addChild(parentNodeRef, childNodeRef, assocTypeQName, assocChildQName);
}
catch (AssociationExistsException aee)
{
throw new ConstraintViolatedException(aee.getMessage());
}
catch (DuplicateChildNodeNameException dcne)
{
throw new ConstraintViolatedException(dcne.getMessage());
}
result.add(assoc);
}
return result;
}
public List addTargets(String sourceNodeId, List entities)
{
List result = new ArrayList<>(entities.size());
NodeRef srcNodeRef = validateNode(sourceNodeId);
for (AssocTarget assoc : entities)
{
String targetNodeId = assoc.getTargetId();
if (targetNodeId == null)
{
throw new InvalidArgumentException("Missing targetId");
}
String assocTypeStr = assoc.getAssocType();
QName assocTypeQName = getAssocType(assocTypeStr);
try
{
NodeRef tgtNodeRef = validateNode(targetNodeId);
nodeAssocService.createAssociation(srcNodeRef, tgtNodeRef, assocTypeQName);
}
catch (AssociationExistsException aee)
{
throw new ConstraintViolatedException("Node association '"+assocTypeStr+"' already exists from "+sourceNodeId+" to "+targetNodeId);
}
catch (IllegalArgumentException iae)
{
// note: for now, we assume it is invalid assocType - alternatively, we could attempt to pre-validate via dictionary.getAssociation
throw new InvalidArgumentException(sourceNodeId+","+assocTypeStr+","+targetNodeId);
}
result.add(assoc);
}
return result;
}
public QName getAssocType(String assocTypeQNameStr)
{
return getAssocType(assocTypeQNameStr, true);
}
public QName getAssocType(String assocTypeQNameStr, boolean mandatory)
{
QName assocType = null;
if ((assocTypeQNameStr != null) && (! assocTypeQNameStr.isEmpty()))
{
assocType = createQName(assocTypeQNameStr);
if (dictionaryService.getAssociation(assocType) == null)
{
throw new InvalidArgumentException("Unknown assocType: " + assocTypeQNameStr);
}
if (EXCLUDED_NS.contains(assocType.getNamespaceURI()))
{
throw new InvalidArgumentException("Invalid assocType: " + assocTypeQNameStr);
}
}
if (mandatory && (assocType == null))
{
throw new InvalidArgumentException("Missing assocType");
}
return assocType;
}
private NodeRef createNodeImpl(NodeRef parentNodeRef, String nodeName, QName nodeTypeQName, Map props, QName assocTypeQName)
{
NodeRef newNode = null;
if (props == null)
{
props = new HashMap<>(1);
}
props.put(ContentModel.PROP_NAME, nodeName);
validatePropValues(props);
QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(nodeName));
try
{
newNode = nodeService.createNode(parentNodeRef, assocTypeQName, assocQName, nodeTypeQName, props).getChildRef();
}
catch (DuplicateChildNodeNameException dcne)
{
// duplicate - name clash
throw new ConstraintViolatedException(dcne.getMessage());
}
ActivityInfo activityInfo = getActivityInfo(parentNodeRef, newNode);
postActivity(Activity_Type.ADDED, activityInfo, false);
return newNode;
}
/**
* Posts activites based on the activity_type.
* If the method is called with aSync=true then a TransactionListener is used post the activity
* afterCommit. Otherwise the activity posting is done synchronously.
* @param activity_type
* @param activityInfo
* @param aSync
*/
protected void postActivity(Activity_Type activity_type, ActivityInfo activityInfo, boolean aSync)
{
if (activityInfo == null) return; //Nothing to do.
String activityType = determineActivityType(activity_type, activityInfo.getFileInfo().isFolder());
if (activityType != null)
{
if (aSync)
{
ActivitiesTransactionListener txListener = new ActivitiesTransactionListener(activityType, activityInfo,
TenantUtil.getCurrentDomain(), Activities.APP_TOOL, Activities.RESTAPI_CLIENT,
poster, retryingTransactionHelper);
AlfrescoTransactionSupport.bindListener(txListener);
}
else
{
poster.postFileFolderActivity(activityType, null, TenantUtil.getCurrentDomain(),
activityInfo.getSiteId(), activityInfo.getParentNodeRef(), activityInfo.getNodeRef(),
activityInfo.getFileName(), Activities.APP_TOOL, Activities.RESTAPI_CLIENT,
activityInfo.getFileInfo());
}
}
}
protected ActivityInfo getActivityInfo(NodeRef parentNodeRef, NodeRef nodeRef)
{
SiteInfo siteInfo = siteService.getSite(nodeRef);
String siteId = (siteInfo != null ? siteInfo.getShortName() : null);
if(siteId != null && !siteId.equals(""))
{
FileInfo fileInfo = fileFolderService.getFileInfo(nodeRef);
if (fileInfo != null)
{
boolean isContent = isSubClass(fileInfo.getType(), ContentModel.TYPE_CONTENT);
if (fileInfo.isFolder() || isContent)
{
return new ActivityInfo(null, parentNodeRef, siteId, fileInfo);
}
}
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("Non-site activity, so ignored " + nodeRef);
}
}
return null;
}
protected static String determineActivityType(Activity_Type activity_type, boolean isFolder)
{
switch (activity_type)
{
case DELETED:
return isFolder ? ActivityType.FOLDER_DELETED:ActivityType.FILE_DELETED;
case ADDED:
return isFolder ? ActivityType.FOLDER_ADDED:ActivityType.FILE_ADDED;
case UPDATED:
if (!isFolder) return ActivityType.FILE_UPDATED;
break;
case DOWNLOADED:
if (!isFolder) return ActivityPoster.DOWNLOADED;
break;
}
return null;
}
// check cm:cmobject (but *not* cm:systemfolder)
private void validateCmObject(QName nodeTypeQName)
{
if (! isSubClass(nodeTypeQName, ContentModel.TYPE_CMOBJECT))
{
throw new InvalidArgumentException("Invalid type: " + nodeTypeQName + " - expected (sub-)type of cm:cmobject");
}
if (isSubClass(nodeTypeQName, ContentModel.TYPE_SYSTEM_FOLDER))
{
throw new InvalidArgumentException("Invalid type: " + nodeTypeQName + " - cannot be (sub-)type of cm:systemfolder");
}
}
// special cases: additional validation of property values (if not done by underlying foundation services)
private void validatePropValues(Map props)
{
String newOwner = (String)props.get(ContentModel.PROP_OWNER);
if (newOwner != null)
{
// validate that user exists
if (! personService.personExists(newOwner))
{
throw new InvalidArgumentException("Unknown owner: "+newOwner);
}
}
}
/**
* Check for special case: additional node validation (pending common lower-level service support)
* for blacklist of system nodes that should not be deleted or locked, eg. Company Home, Sites, Data Dictionary
*
* @param nodeRef
* @param type
* @return
*/
protected boolean isSpecialNode(NodeRef nodeRef, QName type)
{
// Check for Company Home, Sites and Data Dictionary (note: must be tenant-aware)
if (nodeRef.equals(repositoryHelper.getCompanyHome()))
{
return true;
}
else if (type.equals(SiteModel.TYPE_SITES) || type.equals(SiteModel.TYPE_SITE))
{
// note: alternatively, we could inject SiteServiceInternal and use getSitesRoot (or indirectly via node locator)
return true;
}
else
{
String tenantDomain = TenantUtil.getCurrentDomain();
NodeRef ddNodeRef = ddCache.get(tenantDomain);
if (ddNodeRef == null)
{
List ddAssocs = nodeService.getChildAssocs(
repositoryHelper.getCompanyHome(),
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "dictionary"));
if (ddAssocs.size() == 1)
{
ddNodeRef = ddAssocs.get(0).getChildRef();
ddCache.put(tenantDomain, ddNodeRef);
}
}
if (nodeRef.equals(ddNodeRef))
{
return true;
}
}
return false;
}
private boolean isLocked(NodeRef nodeRef, Set aspects)
{
boolean locked = false;
if (((aspects != null) && aspects.contains(ContentModel.ASPECT_LOCKABLE))
|| nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
{
locked = lockService.isLocked(nodeRef);
}
return locked;
}
@Override
public Node updateNode(String nodeId, Node nodeInfo, Parameters parameters)
{
retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Void execute() throws Throwable
{
NodeRef nodeRef = updateNodeImpl(nodeId, nodeInfo, parameters);
ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
postActivity(Activity_Type.UPDATED, activityInfo, false);
return null;
}
}, false, true);
return retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
{
@Override
public Node execute() throws Throwable
{
return getFolderOrDocument(nodeId, parameters);
}
}, false, false);
}
protected NodeRef updateNodeImpl(String nodeId, Node nodeInfo, Parameters parameters)
{
final NodeRef nodeRef = validateOrLookupNode(nodeId, null);
QName nodeTypeQName = getNodeType(nodeRef);
validateCmObject(nodeTypeQName);
Map props = new HashMap<>(0);
if (nodeInfo.getProperties() != null)
{
props = mapToNodeProperties(nodeInfo.getProperties());
}
String name = nodeInfo.getName();
if ((name != null) && (! name.isEmpty()))
{
// update node name if needed - note: if the name is different than existing then this is equivalent of a rename (within parent folder)
props.put(ContentModel.PROP_NAME, name);
}
NodePermissions nodePerms = nodeInfo.getPermissions();
if (nodePerms != null)
{
// Cannot set inherited permissions, only direct (locally set) permissions can be set
if ((nodePerms.getInherited() != null) && (nodePerms.getInherited().size() > 0))
{
throw new InvalidArgumentException("Cannot set *inherited* permissions on this node");
}
// Check inherit from parent value and if it's changed set the new value
if (nodePerms.getIsInheritanceEnabled() != null)
{
if (nodePerms.getIsInheritanceEnabled() != permissionService.getInheritParentPermissions(nodeRef))
{
permissionService.setInheritParentPermissions(nodeRef, nodePerms.getIsInheritanceEnabled());
}
}
// set direct permissions
if ((nodePerms.getLocallySet() != null))
{
// list of all directly set permissions
Set directPerms = new HashSet<>(5);
for (AccessPermission accessPerm : permissionService.getAllSetPermissions(nodeRef))
{
if (accessPerm.isSetDirectly())
{
directPerms.add(accessPerm);
}
}
//
// replace (or clear) set of direct permissions
//
// TODO cleanup the way we replace permissions (ie. add, update and delete)
// check if same permission is sent more than once
if (hasDuplicatePermissions(nodePerms.getLocallySet()))
{
throw new InvalidArgumentException("Duplicate node permissions, there is more than one permission with the same authority and name!");
}
for (NodePermissions.NodePermission nodePerm : nodePerms.getLocallySet())
{
String permName = nodePerm.getName();
String authorityId = nodePerm.getAuthorityId();
AccessStatus accessStatus = AccessStatus.ALLOWED;
if (nodePerm.getAccessStatus() != null)
{
accessStatus = AccessStatus.valueOf(nodePerm.getAccessStatus());
}
if (authorityId == null || authorityId.isEmpty())
{
throw new InvalidArgumentException("Authority Id is expected.");
}
if (permName == null || permName.isEmpty())
{
throw new InvalidArgumentException("Permission name is expected.");
}
if (((!authorityId.equals(PermissionService.ALL_AUTHORITIES) && (!authorityService.authorityExists(authorityId)))))
{
throw new InvalidArgumentException("Cannot set permissions on this node - unknown authority: " + authorityId);
}
AccessPermission existing = null;
boolean addPerm = true;
boolean updatePerm = false;
// If the permission already exists but with different access status it will be updated
for (AccessPermission accessPerm : directPerms)
{
if (accessPerm.getAuthority().equals(authorityId) && accessPerm.getPermission().equals(permName))
{
existing = accessPerm;
addPerm = false;
if (accessPerm.getAccessStatus() != accessStatus)
{
updatePerm = true;
}
break;
}
}
if (existing != null)
{
// ignore existing permissions
directPerms.remove(existing);
}
if (addPerm || updatePerm)
{
try
{
permissionService.setPermission(nodeRef, authorityId, permName, (accessStatus == AccessStatus.ALLOWED));
}
catch (UnsupportedOperationException e)
{
throw new InvalidArgumentException("Cannot set permissions on this node - unknown access level: " + permName);
}
}
}
// remove any remaining direct perms
for (AccessPermission accessPerm : directPerms)
{
permissionService.deletePermission(nodeRef, accessPerm.getAuthority(), accessPerm.getPermission());
}
}
}
String nodeType = nodeInfo.getNodeType();
if ((nodeType != null) && (! nodeType.isEmpty()))
{
// update node type - ensure that we are performing a specialise (we do not support generalise)
QName destNodeTypeQName = createQName(nodeType);
if ((! destNodeTypeQName.equals(nodeTypeQName)) &&
isSubClass(destNodeTypeQName, nodeTypeQName) &&
(! isSubClass(destNodeTypeQName, ContentModel.TYPE_SYSTEM_FOLDER)))
{
nodeService.setType(nodeRef, destNodeTypeQName);
}
else
{
throw new InvalidArgumentException("Failed to change (specialise) node type - from "+nodeTypeQName+" to "+destNodeTypeQName);
}
}
NodeRef parentNodeRef = nodeInfo.getParentId();
if (parentNodeRef != null)
{
NodeRef currentParentNodeRef = getParentNodeRef(nodeRef);
if (currentParentNodeRef == null)
{
// implies root (Company Home) hence return 403 here
throw new PermissionDeniedException();
}
if (! currentParentNodeRef.equals(parentNodeRef))
{
//moveOrCopy(nodeRef, parentNodeRef, name, false); // not currently supported - client should use explicit POST /move operation instead
throw new InvalidArgumentException("Cannot update parentId of "+nodeId+" via PUT /nodes/{nodeId}. Please use explicit POST /nodes/{nodeId}/move operation instead");
}
}
List aspectNames = nodeInfo.getAspectNames();
updateCustomAspects(nodeRef, aspectNames, EXCLUDED_ASPECTS);
if (props.size() > 0)
{
validatePropValues(props);
try
{
// update node properties - note: null will unset the specified property
nodeService.addProperties(nodeRef, props);
}
catch (DuplicateChildNodeNameException dcne)
{
throw new ConstraintViolatedException(dcne.getMessage());
}
}
return nodeRef;
}
@Override
public Node moveOrCopyNode(String sourceNodeId, String targetParentId, String name, Parameters parameters, boolean isCopy)
{
if ((sourceNodeId == null) || (sourceNodeId.isEmpty()))
{
throw new InvalidArgumentException("Missing sourceNodeId");
}
if ((targetParentId == null) || (targetParentId.isEmpty()))
{
throw new InvalidArgumentException("Missing targetParentId");
}
final NodeRef parentNodeRef = validateOrLookupNode(targetParentId, null);
final NodeRef sourceNodeRef = validateOrLookupNode(sourceNodeId, null);
FileInfo fi = moveOrCopyImpl(sourceNodeRef, parentNodeRef, name, isCopy);
return getFolderOrDocument(fi.getNodeRef().getId(), parameters);
}
public void updateCustomAspects(NodeRef nodeRef, List aspectNames, List excludedAspects)
{
if (aspectNames != null)
{
// update aspects - note: can be empty (eg. to remove existing aspects+properties) but not cm:auditable, sys:referencable, sys:localized
Set aspectQNames = mapToNodeAspects(aspectNames);
Set existingAspects = nodeService.getAspects(nodeRef);
Set aspectsToAdd = new HashSet<>(3);
Set aspectsToRemove = new HashSet<>(3);
for (QName aspectQName : aspectQNames)
{
if (EXCLUDED_NS.contains(aspectQName.getNamespaceURI()) || excludedAspects.contains(aspectQName) || aspectQName.equals(ContentModel.ASPECT_AUDITABLE))
{
continue; // ignore
}
if (! existingAspects.contains(aspectQName))
{
aspectsToAdd.add(aspectQName);
}
}
for (QName existingAspect : existingAspects)
{
if (EXCLUDED_NS.contains(existingAspect.getNamespaceURI()) || excludedAspects.contains(existingAspect) || existingAspect.equals(ContentModel.ASPECT_AUDITABLE))
{
continue; // ignore
}
if (! aspectQNames.contains(existingAspect))
{
aspectsToRemove.add(existingAspect);
}
}
// Note: for now, if aspectNames are sent then all that are required should be sent (to avoid properties from other existing aspects being removed)
// TODO: optional PATCH mechanism to add one new new aspect (with some related aspect properties) without affecting existing aspects/properties
for (QName aQName : aspectsToRemove)
{
if (aQName.equals(QuickShareModel.ASPECT_QSHARE))
{
String qSharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
if (qSharedId != null)
{
// note: for now, go via QuickShareLinks (rather than QuickShareService) to ensure consistent permission checks
// alternatively we could disallow (or ignore) "qshare:shared" aspect removal
quickShareLinks.delete(qSharedId, null);
}
}
nodeService.removeAspect(nodeRef, aQName);
}
for (QName aQName : aspectsToAdd)
{
if (aQName.equals(QuickShareModel.ASPECT_QSHARE))
{
// note: for now, go via QuickShareLinks (rather than QuickShareService) to ensure consistent permission checks
// alternatively we could disallow (or ignore) "qshare:shared" aspect addition
QuickShareLink qs = new QuickShareLink();
qs.setNodeId(nodeRef.getId());
quickShareLinks.create(Collections.singletonList(qs), null);
}
nodeService.addAspect(nodeRef, aQName, null);
}
}
}
protected FileInfo moveOrCopyImpl(NodeRef nodeRef, NodeRef parentNodeRef, String name, boolean isCopy)
{
String targetParentId = parentNodeRef.getId();
try
{
if (isCopy)
{
// copy
FileInfo newFileInfo = fileFolderService.copy(nodeRef, parentNodeRef, name);
if (newFileInfo.getNodeRef().equals(nodeRef))
{
// copy did not happen - eg. same parent folder and name (name can be null or same)
throw new FileExistsException(nodeRef, "");
}
return newFileInfo;
}
else
{
// move
if ((! nodeRef.equals(parentNodeRef)) && isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
throw new PermissionDeniedException("Cannot move: "+nodeRef.getId());
}
// updating "parentId" means moving primary parent !
// note: in the future (as and when we support secondary parent/child assocs) we may also
// wish to select which parent to "move from" (in case where the node resides in multiple locations)
return fileFolderService.move(nodeRef, parentNodeRef, name);
}
}
catch (InvalidNodeRefException inre)
{
throw new EntityNotFoundException(targetParentId);
}
catch (FileNotFoundException fnfe)
{
// convert checked exception
throw new EntityNotFoundException(targetParentId);
}
catch (FileExistsException fee)
{
// duplicate - name clash
throw new ConstraintViolatedException("Name already exists in target parent: "+name);
}
catch (FileFolderServiceImpl.InvalidTypeException ite)
{
throw new InvalidArgumentException("Invalid type of target parent: "+targetParentId);
}
}
@Override
public BinaryResource getContent(String fileNodeId, Parameters parameters, boolean recordActivity)
{
final NodeRef nodeRef = validateNode(fileNodeId);
return getContent(nodeRef, parameters, recordActivity);
}
@Override
public BinaryResource getContent(NodeRef nodeRef, Parameters parameters, boolean recordActivity)
{
if (!nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null, false))
{
throw new InvalidArgumentException("NodeId of content is expected: " + nodeRef.getId());
}
Map nodeProps = nodeService.getProperties(nodeRef);
ContentData cd = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT);
String name = (String) nodeProps.get(ContentModel.PROP_NAME);
org.alfresco.rest.framework.resource.content.ContentInfo ci = null;
String mimeType = null;
if (cd != null)
{
mimeType = cd.getMimetype();
ci = new org.alfresco.rest.framework.resource.content.ContentInfoImpl(mimeType, cd.getEncoding(), cd.getSize(), cd.getLocale());
}
// By default set attachment header (with filename) unless attachment=false *and* content type is pre-configured as non-attach
boolean attach = true;
String attachment = parameters.getParameter("attachment");
if (attachment != null)
{
Boolean a = Boolean.valueOf(attachment);
if (!a)
{
if (nonAttachContentTypes.contains(mimeType))
{
attach = false;
}
else
{
logger.warn("Ignored attachment=false for "+nodeRef.getId()+" since "+mimeType+" is not in the whitelist for non-attach content types");
}
}
}
String attachFileName = (attach ? name : null);
if (recordActivity)
{
final ActivityInfo activityInfo = getActivityInfo(getParentNodeRef(nodeRef), nodeRef);
postActivity(Activity_Type.DOWNLOADED, activityInfo, true);
}
return new NodeBinaryResource(nodeRef, ContentModel.PROP_CONTENT, ci, attachFileName);
}
@Override
public Node updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
{
if (contentInfo.getMimeType().toLowerCase().startsWith("multipart"))
{
throw new UnsupportedMediaTypeException("Cannot update using "+contentInfo.getMimeType());
}
final NodeRef nodeRef = validateNode(fileNodeId);
if (! nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null, false))
{
throw new InvalidArgumentException("NodeId of content is expected: " + nodeRef.getId());
}
Boolean versionMajor = null;
String str = parameters.getParameter(PARAM_VERSION_MAJOR);
if (str != null)
{
versionMajor = new Boolean(str);
}
String versionComment = parameters.getParameter(PARAM_VERSION_COMMENT);
String fileName = parameters.getParameter(PARAM_NAME);
if (fileName != null)
{
// optionally rename, before updating the content
nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, fileName);
}
else
{
fileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
}
return updateExistingFile(null, nodeRef, fileName, contentInfo, stream, parameters, versionMajor, versionComment);
}
private Node updateExistingFile(NodeRef parentNodeRef, NodeRef nodeRef, String fileName, BasicContentInfo contentInfo, InputStream stream, Parameters parameters, Boolean versionMajor, String versionComment)
{
boolean isVersioned = versionService.isVersioned(nodeRef);
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
try
{
writeContent(nodeRef, fileName, stream, true);
if ((isVersioned) || (versionMajor != null) || (versionComment != null) )
{
VersionType versionType = null;
if (versionMajor != null)
{
versionType = (versionMajor ? VersionType.MAJOR : VersionType.MINOR);
}
else
{
// note: it is possible to have versionable aspect but no versions (=> no version label)
if ((! isVersioned) || (nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL) == null))
{
versionType = VersionType.MAJOR;
}
else
{
versionType = VersionType.MINOR;
}
}
createVersion(nodeRef, isVersioned, versionType, versionComment);
}
ActivityInfo activityInfo = getActivityInfo(parentNodeRef, nodeRef);
postActivity(Activity_Type.UPDATED, activityInfo, false);
extractMetadata(nodeRef);
}
finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
}
return getFolderOrDocumentFullInfo(nodeRef, null, null, parameters);
}
private void writeContent(NodeRef nodeRef, String fileName, InputStream stream, boolean guessEncoding)
{
try
{
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
String mimeType = mimetypeService.guessMimetype(fileName);
if ((mimeType != null) && (!mimeType.equals(MimetypeMap.MIMETYPE_BINARY)))
{
// quick/weak guess based on file extension
writer.setMimetype(mimeType);
} else
{
// stronger guess based on file stream
writer.guessMimetype(fileName);
}
InputStream is = null;
if (guessEncoding)
{
is = new BufferedInputStream(stream);
is.mark(1024);
writer.setEncoding(guessEncoding(is, mimeType, false));
try
{
is.reset();
} catch (IOException ioe)
{
if (logger.isWarnEnabled())
{
logger.warn("Failed to reset stream after trying to guess encoding: " + ioe.getMessage());
}
}
} else
{
is = stream;
}
writer.putContent(is);
}
catch (ContentQuotaException cqe)
{
throw new InsufficientStorageException();
}
catch (ContentLimitViolationException clv)
{
throw new RequestEntityTooLargeException(clv.getMessage());
}
catch (ContentIOException cioe)
{
if (cioe.getCause() instanceof NodeLockedException)
{
throw (NodeLockedException)cioe.getCause();
}
throw cioe;
}
}
private String guessEncoding(InputStream in, String mimeType, boolean close)
{
String encoding = "UTF-8";
try
{
if (in != null)
{
Charset charset = mimetypeService.getContentCharsetFinder().getCharset(in, mimeType);
encoding = charset.name();
}
}
finally
{
try
{
if (close && (in != null))
{
in.close();
}
}
catch (IOException ioe)
{
if (logger.isWarnEnabled())
{
logger.warn("Failed to close stream after trying to guess encoding: " + ioe.getMessage());
}
}
}
return encoding;
}
protected void createVersion(NodeRef nodeRef, boolean isVersioned, VersionType versionType, String reason)
{
if (! isVersioned)
{
// Ensure versioning is enabled for the file (autoVersion = true, autoVersionProps = false)
Map props = new HashMap<>(2);
props.put(ContentModel.PROP_AUTO_VERSION, true);
props.put(ContentModel.PROP_AUTO_VERSION_PROPS, false);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
}
Map versionProperties = new HashMap<>(2);
versionProperties.put(VersionModel.PROP_VERSION_TYPE, versionType);
if (reason != null)
{
versionProperties.put(VersionModel.PROP_DESCRIPTION, reason);
}
versionService.createVersion(nodeRef, versionProperties);
}
@Override
public Node upload(String parentFolderNodeId, FormData formData, Parameters parameters)
{
if (formData == null || !formData.getIsMultiPart())
{
throw new InvalidArgumentException("The request content-type is not multipart: "+parentFolderNodeId);
}
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null);
if (!nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null, false))
{
throw new InvalidArgumentException("NodeId of folder is expected: " + parentNodeRef.getId());
}
String fileName = null;
Content content = null;
boolean autoRename = false;
QName nodeTypeQName = ContentModel.TYPE_CONTENT;
boolean overwrite = false; // If a fileName clashes for a versionable file
Boolean versionMajor = null;
String versionComment = null;
String relativePath = null;
String renditionNames = null;
Map qnameStrProps = new HashMap<>();
Map properties = null;
for (FormData.FormField field : formData.getFields())
{
switch (field.getName().toLowerCase())
{
case "name":
String str = getStringOrNull(field.getValue());
if ((str != null) && (! str.isEmpty()))
{
fileName = str;
}
break;
case "filedata":
if (field.getIsFile())
{
fileName = (fileName != null ? fileName : field.getFilename());
content = field.getContent();
}
break;
case "autorename":
autoRename = Boolean.valueOf(field.getValue());
break;
case "nodetype":
nodeTypeQName = createQName(getStringOrNull(field.getValue()));
if (! isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT))
{
throw new InvalidArgumentException("Can only upload type of cm:content: " + nodeTypeQName);
}
break;
case "overwrite":
overwrite = Boolean.valueOf(field.getValue());
break;
case "majorversion":
versionMajor = Boolean.valueOf(field.getValue());
break;
case "comment":
versionComment = getStringOrNull(field.getValue());
break;
case "relativepath":
relativePath = getStringOrNull(field.getValue());
break;
case "renditions":
renditionNames = getStringOrNull(field.getValue());
break;
default:
{
final String propName = field.getName();
if (propName.indexOf(QName.NAMESPACE_PREFIX) > -1)
{
qnameStrProps.put(propName, field.getValue());
}
}
}
}
// MNT-7213 When alf_data runs out of disk space, Share uploads
// result in a success message, but the files do not appear.
if (formData.getFields().length == 0)
{
throw new ConstraintViolatedException("No disk space available");
}
// Ensure mandatory file attributes have been located. Need either
// destination, or site + container or updateNodeRef
if ((fileName == null) || fileName.isEmpty() || (content == null))
{
throw new InvalidArgumentException("Required parameters are missing");
}
if (autoRename && overwrite)
{
throw new InvalidArgumentException("Both 'overwrite' and 'autoRename' should not be true when uploading a file");
}
// if requested, make (get or create) path
parentNodeRef = getOrCreatePath(parentNodeRef, relativePath);
final QName assocTypeQName = ContentModel.ASSOC_CONTAINS;
final Set renditions = getRequestedRenditions(renditionNames);
try
{
// Map the given properties, if any.
if (qnameStrProps.size() > 0)
{
properties = mapToNodeProperties(qnameStrProps);
}
/*
* Existing file handling
*/
NodeRef existingFile = nodeService.getChildByName(parentNodeRef, assocTypeQName, fileName);
if (existingFile != null)
{
// File already exists, decide what to do
if (autoRename)
{
// attempt to find a unique name
fileName = findUniqueName(parentNodeRef, fileName);
// drop-through !
}
else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE))
{
// overwrite existing (versionable) file
BasicContentInfo contentInfo = new ContentInfoImpl(content.getMimetype(), content.getEncoding(), -1, null);
return updateExistingFile(parentNodeRef, existingFile, fileName, contentInfo, content.getInputStream(), parameters, versionMajor, versionComment);
}
else
{
// name clash (and no autoRename or overwrite)
throw new ConstraintViolatedException(fileName + " already exists.");
}
}
// Note: pending REPO-159, we currently auto-enable versioning on new upload (but not when creating empty file)
if (versionMajor == null)
{
versionMajor = true;
}
// Create a new file.
NodeRef nodeRef = createNewFile(parentNodeRef, fileName, nodeTypeQName, content, properties, assocTypeQName, parameters, versionMajor, versionComment);
// Create the response
final Node fileNode = getFolderOrDocumentFullInfo(nodeRef, parentNodeRef, nodeTypeQName, parameters);
// RA-1052
try
{
List thumbnailDefs = getThumbnailDefs(renditions);
requestRenditions(thumbnailDefs, fileNode);
}
catch (Exception ex)
{
// Note: The log level is not 'error' as it could easily fill out the log file, especially in the Cloud.
if (logger.isDebugEnabled())
{
// Don't throw the exception as we don't want the the upload to fail, just log it.
logger.debug("Asynchronous request to create a rendition upon upload failed: " + ex.getMessage());
}
}
return fileNode;
// Do not clean formData temp files to allow for retries.
// Temp files will be deleted later when GC call DiskFileItem#finalize() method or by temp file cleaner.
}
catch (ApiException apiEx)
{
// As this is an public API fwk exception, there is no need to convert it, so just throw it.
throw apiEx;
}
catch (AccessDeniedException ade)
{
throw new PermissionDeniedException(ade.getMessage());
}
catch (Exception ex)
{
/*
* NOTE: Do not clean formData temp files to allow for retries. It's
* possible for a temp file to remain if max retry attempts are
* made, but this is rare, so leave to usual temp file cleanup.
*/
throw new ApiException("Unexpected error occurred during upload of new content.", ex);
}
}
private NodeRef createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType, Content content, Map props, QName assocTypeQName, Parameters params,
Boolean versionMajor, String versionComment)
{
NodeRef nodeRef = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName);
if (content == null)
{
// Write "empty" content
writeContent(nodeRef, fileName, new ByteArrayInputStream("".getBytes()), false);
}
else
{
// Write content
writeContent(nodeRef, fileName, content.getInputStream(), true);
}
if ((versionMajor != null) || (versionComment != null))
{
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
try
{
// by default, first version is major, unless specified otherwise
VersionType versionType = VersionType.MAJOR;
if ((versionMajor != null) && (!versionMajor))
{
versionType = VersionType.MINOR;
}
createVersion(nodeRef, false, versionType, versionComment);
extractMetadata(nodeRef);
} finally
{
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
}
}
return nodeRef;
}
private String getStringOrNull(String value)
{
if (StringUtils.isNotEmpty(value))
{
return value.equalsIgnoreCase("null") ? null : value;
}
return null;
}
private List getThumbnailDefs(Set renditionNames)
{
List thumbnailDefs = null;
if (renditionNames != null)
{
// If thumbnail generation has been configured off, then don't bother.
if (!thumbnailService.getThumbnailsEnabled())
{
throw new DisabledServiceException("Thumbnail generation has been disabled.");
}
thumbnailDefs = new ArrayList<>(renditionNames.size());
ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
for (String renditionName : renditionNames)
{
// Use the thumbnail registry to get the details of the thumbnail
ThumbnailDefinition thumbnailDef = registry.getThumbnailDefinition(renditionName);
if (thumbnailDef == null)
{
throw new NotFoundException(renditionName + " is not registered.");
}
thumbnailDefs.add(thumbnailDef);
}
}
return thumbnailDefs;
}
private Set getRequestedRenditions(String renditionsParam)
{
if (renditionsParam == null)
{
return null;
}
String[] renditionNames = renditionsParam.split(",");
// Temporary - pending future improvements to thumbnail service to minimise chance of
// missing/failed thumbnails (when requested/generated 'concurrently')
if (renditionNames.length > 1)
{
throw new InvalidArgumentException("Please specify one rendition entity id only");
}
Set renditions = new LinkedHashSet<>(renditionNames.length);
for (String name : renditionNames)
{
name = name.trim();
if (!name.isEmpty())
{
renditions.add(name.trim());
}
}
return renditions;
}
private void requestRenditions(List thumbnailDefs, Node fileNode)
{
if (thumbnailDefs != null)
{
ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
for (ThumbnailDefinition thumbnailDef : thumbnailDefs)
{
NodeRef sourceNodeRef = fileNode.getNodeRef();
String mimeType = fileNode.getContent().getMimeType();
long size = fileNode.getContent().getSizeInBytes();
// Check if anything is currently available to generate thumbnails for the specified mimeType
if (! registry.isThumbnailDefinitionAvailable(null, mimeType, size, sourceNodeRef, thumbnailDef))
{
throw new InvalidArgumentException("Unable to create thumbnail '" + thumbnailDef.getName() + "' for " +
mimeType + " as no transformer is currently available.");
}
Action action = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, sr);
// Queue async creation of thumbnail
actionService.executeAction(action, sourceNodeRef, true, true);
}
}
}
/**
* Extracts the given node metadata asynchronously.
*
* The overwrite policy controls which if any parts of the document's properties are updated from this.
*/
private void extractMetadata(NodeRef nodeRef)
{
final String actionName = ContentMetadataExtracter.EXECUTOR_NAME;
ActionDefinition actionDef = actionService.getActionDefinition(actionName);
if (actionDef != null)
{
Action action = actionService.createAction(actionName);
actionService.executeAction(action, nodeRef);
}
}
/**
* Creates a unique file name, if the upload component was configured to
* find a new unique name for clashing filenames.
*
* @param parentNodeRef the parent node
* @param fileName the original fileName
* @return a new file name
*/
private String findUniqueName(NodeRef parentNodeRef, String fileName)
{
int counter = 1;
String tmpFilename;
NodeRef existingFile;
do
{
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex == 0)
{
// File didn't have a proper 'name' instead it
// had just a suffix and started with a ".", create "1.txt"
tmpFilename = counter + fileName;
}
else if (dotIndex > 0)
{
// Filename contained ".", create "fileName-1.txt"
tmpFilename = fileName.substring(0, dotIndex) + "-" + counter + fileName.substring(dotIndex);
}
else
{
// Filename didn't contain a dot at all, create "fileName-1"
tmpFilename = fileName + "-" + counter;
}
existingFile = nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, tmpFilename);
counter++;
} while (existingFile != null);
return tmpFilename;
}
/**
* Helper to create a QName from either a fully qualified or short-name QName string
*
* @param qnameStr Fully qualified or short-name QName string
* @return QName
*/
public QName createQName(String qnameStr)
{
try
{
QName qname;
if (qnameStr.indexOf(QName.NAMESPACE_BEGIN) != -1)
{
qname = QName.createQName(qnameStr);
}
else
{
qname = QName.createQName(qnameStr, namespaceService);
}
return qname;
}
catch (Exception ex)
{
String msg = ex.getMessage();
if (msg == null)
{
msg = "";
}
throw new InvalidArgumentException(qnameStr + " isn't a valid QName. " + msg);
}
}
/**
* Helper to create a QName from either a fully qualified or short-name QName string
*
* @param qnameStrList list of fully qualified or short-name QName string
* @param excludedProps
* @return a list of {@code QName} objects
*/
protected List createQNames(List qnameStrList, List excludedProps)
{
String PREFIX = PARAM_INCLUDE_PROPERTIES +"/";
List result = new ArrayList<>(qnameStrList.size());
for (String str : qnameStrList)
{
if (str.startsWith(PREFIX))
{
str = str.substring(PREFIX.length());
}
QName name = createQName(str);
if (!excludedProps.contains(name))
{
result.add(name);
}
}
return result;
}
@Override
public Node lock(String nodeId, LockInfo lockInfo, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
throw new PermissionDeniedException("Current user doesn't have permission to lock node " + nodeId);
}
if (!nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null, false))
{
throw new InvalidArgumentException("Node of type cm:content or a subtype is expected: " + nodeId);
}
lockInfo = validateLockInformation(lockInfo);
lockService.lock(nodeRef, lockInfo.getMappedType(), lockInfo.getTimeToExpire(), lockInfo.getLifetime());
return getFolderOrDocument(nodeId, parameters);
}
private LockInfo validateLockInformation(LockInfo lockInfo)
{
// Set default values for the lock details.
if (lockInfo.getType() == null)
{
lockInfo.setType(LockInfo.LockType2.ALLOW_OWNER_CHANGES.name());
}
if (lockInfo.getLifetime() == null)
{
lockInfo.setLifetime(Lifetime.PERSISTENT.name());
}
if (lockInfo.getTimeToExpire() == null)
{
lockInfo.setTimeToExpire(0);
}
return lockInfo;
}
@Override
public Node unlock(String nodeId, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
throw new PermissionDeniedException("Current user doesn't have permission to unlock node " + nodeId);
}
if (!lockService.isLocked(nodeRef))
{
throw new IntegrityException("Can't unlock node " + nodeId + " because it isn't locked", null);
}
lockService.unlock(nodeRef);
return getFolderOrDocument(nodeId, parameters);
}
/**
* Checks if same permission is sent more than once
* @param locallySetPermissions
* @return
*/
private boolean hasDuplicatePermissions(List locallySetPermissions)
{
boolean duplicate = false;
if (locallySetPermissions != null)
{
HashSet temp = new HashSet<>(locallySetPermissions.size());
for (NodePermissions.NodePermission permission : locallySetPermissions)
{
temp.add(permission);
}
duplicate = (locallySetPermissions.size() != temp.size());
}
return duplicate;
}
/**
* @author Jamal Kaabi-Mofrad
*/
/*
private static class ContentInfoWrapper implements BasicContentInfo
{
private String mimeType;
private String encoding;
public String getEncoding()
{
return encoding;
}
public String getMimeType()
{
return mimeType;
}
ContentInfoWrapper(BasicContentInfo basicContentInfo)
{
if (basicContentInfo != null)
{
this.mimeType = basicContentInfo.getMimeType();
this.encoding = basicContentInfo.getEncoding();
}
}
ContentInfoWrapper(ContentInfo contentInfo)
{
if (contentInfo != null)
{
this.mimeType = contentInfo.getMimeType();
this.encoding = contentInfo.getEncoding();
}
}
ContentInfoWrapper(Content content)
{
if (content != null && StringUtils.isNotEmpty(content.getMimetype()))
{
try
{
// TODO I think it makes sense to push contentType parsing into org.springframework.extensions.webscripts.servlet.FormData
MediaType media = MediaType.parseMediaType(content.getMimetype());
this.mimeType = media.getType() + '/' + media.getSubtype();
if (media.getCharSet() != null)
{
this.encoding = media.getCharSet().name();
}
}
catch (InvalidMediaTypeException ime)
{
throw new InvalidArgumentException(ime.getMessage());
}
}
}
}
*/
protected NodeService getNodeService()
{
return nodeService;
}
protected DictionaryService getDictionaryService()
{
return dictionaryService;
}
protected FileFolderService getFileFolderService()
{
return fileFolderService;
}
protected NamespaceService getNamespaceService()
{
return namespaceService;
}
protected PermissionService getPermissionService()
{
return permissionService;
}
protected MimetypeService getMimetypeService()
{
return mimetypeService;
}
protected ContentService getContentService()
{
return contentService;
}
protected ActionService getActionService()
{
return actionService;
}
protected VersionService getVersionService()
{
return versionService;
}
protected PersonService getPersonService()
{
return personService;
}
protected OwnableService getOwnableService()
{
return ownableService;
}
protected AuthorityService getAuthorityService()
{
return authorityService;
}
protected ThumbnailService getThumbnailService()
{
return thumbnailService;
}
protected SiteService getSiteService()
{
return siteService;
}
protected ActivityPoster getPoster()
{
return poster;
}
protected RetryingTransactionHelper getRetryingTransactionHelper()
{
return retryingTransactionHelper;
}
protected NodeAssocService getNodeAssocService()
{
return nodeAssocService;
}
protected LockService getLockService()
{
return lockService;
}
protected VirtualStore getSmartStore()
{
return smartStore;
}
protected QuickShareLinks getQuickShareLinks()
{
return quickShareLinks;
}
protected Repository getRepositoryHelper()
{
return repositoryHelper;
}
}