mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
20678: DAO5 branch: Preparation for merge back to HEAD 20689: Merged DAO4 to DAO5 - Removed all 'dbscripts/create/3.x/SomeDialect' and replaced with 'dbscripts/create/SomeDialect' DB create scripts are taken from latest DAO4 - TODO: FixAuthoritiesCrcValuesPatch needs query implementation in PatchDAO Merged DAO3 to DAO4 - Reapplied fixes for ALF-713 (race condition on Usages) 19350: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-2 to BRANCHES/DEV/V3.3-DAO-REFACTOR-3: 18939: SAIL-4 :2nd stage branch for DAO refactor off HEAD rev 18898 18948: Merged V3.3-DAO-REFACTOR to V3.3-DAO-REFACTOR-2 18202: Dev branch for DAO refactor 18252: SAIL-233: QName.hbm.xml 18295: Added missing CREATE TABLE statements for QName-related code 18324: SAIL-234: Node.hbm.xml: Node aspects initial integration 18355: Added 'setValue' method to manually update the cached value 18356: MV property stressing lowered to speed test up 18357: SAIL-234: Node.hbm.xml 18376: Pulled all Alfresco-related create SQL into script 18389: SAIL-234: Permissions DAO refactor - initial checkpoint 18390: Formatting only (line-endings) 18400: SAIL-234: Node.hbm.xml 18418: SAIL-234: Node.hbm.xml: 'alf_node_assoc' CRUD 18429: SAIL-234: Node.hbm.xml: Cleaned out all Hibernate references to NodeAssocImpl 18457: SAIL-234: Permissions DAO refactor 18959: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18479: SAIL-234: Node.hbm.xml - fix updateNode (missing id when saving oldDummyNode) 18482: SAIL-235: remove Permissions.hbm.xml 18517: SAIL-235: Permissions DAO refactor 18523: SAIL-234: Node.hbm.xml 18524: SAIL-235: Permissions DAO refactor 18960: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18533: Flipped back to Windows line endings 18535: Formatting-only (eol) 18540: Formatting-only (eol) 18541: SAIL-235: Permissions DAO refactor 18543: SAIL-234: Node.hbm.xml: Start alf_store changes 18567: SAIL-235: Permissions DAO refactor 18596: SAIL-305: Alfresco DDL - formatted/rationalized and added missing indexes & fk constraints 18603: SAIL-311: Minor cleanup for schema upgrade scripts (V3.3) 18604: SAIL-311: Remove empty dirs 18619: SAIL-274: Locale.hbm.xml 18621: Added method to create default ACL 18622: SAIL-234: Node.hbm.xml: Store, Transaction, Server and some node 18624: Formatting only (eol) 18631: SAIL-235: Permissions DAO refactor 18633: SAIL-235: Permissions DAO refactor - do not expose CRUD for AceContext (or AuthorityAlias) since currently unused 18639: getLocale(Locale) should return null if it doesn't exist 18640: SAIL-234 NodeDAO: More replacement of node queries and updates 18648: SAIL-310: Create SQL script for core repo tables (All DB ports) 18651: SAIL-234 NodeDAO: Moves across stores handle presence of target deleted nodes 18961: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18658: SAIL-274 Locale DAO: Missing getValueKey() method 18662: SAIL-235: Permissions DAO refactor - further cleanup (of DbAccessControlList usage, including copyACLs) 18664: DB scripts porting for PostgreSQL finished. 18668: SAIL-234 Node DAO: Note in case Transaction Change ID is dropped from indexes 18669: SAIL-234 Node DAO: deleteNode and archive (store move) fixes 18672: DB scripts porting for Oracle finished. 18675: SAIL-235: Permissions DAO refactor 18677: DB scripts porting for DB2 finished. 18964: Merged DEV/V3.3-DAO-REFACTOR to DEV/V3.3-DAO-REFACTOR-2 18687: Execute a callback with retries 18688: SAIL-234 Node DAO: Child association creation 18690: SAIL-234 Node DAO: Comment out raw creation of stores as it breaks subsequent bootstrap checks 18691: SAIL-234 Node DAO: More replacement of alf_child_assoc handling 18713: Commented about needing a more efficient removeChildAssociation method 18714: SAIL-234 Node DAO: Replaced queries on alf_child_assoc 18715: SAIL-234 Node DAO: More alf_child_assoc query replacement 18727: SAIL-234 Node DAO: alf_child_assoc queries complete 18737: SAIL-234 Node DAO: Tweaks to newNode and implemented prependPaths 18741: SAIL-234 and SAIL-334: Moved UsageDelta Hibernate code and queries over to UsageDeltaDAO 18748: SAIL-234 Node DAO: fix NPE (EditionServiceImplTest) 18769: SAIL-234 Node DAO: alf_node_properties ground work 18786: SAIL-234 Node DAO: alf_node_properties and cm:auditable properties 18810: Added EqualsHelper.getMapComparison 18813: TransactionalCache propagates cache clears and removals during rollback 18826: SAIL-234 Node DAO: Moved over sundry references to NodeDaoService to NodeDAO 18849: SAIL-237: UsageDelta.hbm.xml - eol formatting only (including removal of unwanted svn:eol-style=native property) 18869: SAIL-234 NodeDAO: Fixed more references to 'nodeDaoService' 18895: SAIL-234 NodeDAO: Queries for alf_transaction 18899: SAIL-234 Node DAO: Fixed bean fetching for 'nodeDAO' 18909: SAIL-234 NodeDAO: Fixes to getNodeRefStatus and various txn queries 18916: SAIL-234 NodeDAO: Fixed moveNode alf_child_assoc updates 18922: SAIL-235: DAO refactoring: Permission.hbm.xml 18930: SAIL-235: DAO refactoring: Permission.hbm.xml 18932: SAIL-234 NodeDAO: Fixing up gotchas, javadocs and some naming 18933: SAIL-234 NodeDAO: Minor neatening 18935: SAIL-234 Node DAO: Caches for ID to NodeRef and StoreRef 18936: EHCache config files line endings 18938: SAIL-237: Usage DAO refactor - initial checkpoint 18945: SAIL-235: DAO refactoring: Permission.hbm.xml. Move Node. 18975: Fix for move-node ACL jiggery-pokery 19067: SAIL-4: fix VersionHistoryImpl.getSuccessors (causing VersionServiceImplTest.testGetVersionHistorySameWorkspace failure) 19068: SAIL-234: fix VersionMigratorTest.testMigrateOneVersion 19074: SAIL-237: Usage DAO - update to common iBatis mapping pattern(s) to ease DB porting 19076: SAIL-231: Activities DAO - update to common iBatis mapping pattern(s) 19077: SAIL-232: AppliedPatch DAO - minor cleanup (comments & formatting only) 19092: Merging HEAD to DEV/V3.3-DAO-REFACTOR-2 18973: Temporarily comment out AVMTestSuite and run AVM tests individually 19056: AVM unit test improvements 19097: SAIL-235: DAO refactoring: Permission.hbm.xml: Additional index to support queries to find the id and acl id for the primary children of a node. 19185: SAIL-238: Permissions DAO - (minor) update to common iBatis mapping pattern 19289: SAIL-234 NodeDAO: Node cache replaces NodeRef cache 19302: SAIL-234 Node DAO: Added cache for node properties 19318: SAIL-4: AVM DAO - (minor) update to common iBatis mapping pattern 20690: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-4 to BRANCHES/DEV/V3.3-DAO-REFACTOR-5: 20063: (RECORD ONLY) DAO refactor branch V4 20146: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19401: SAIL-234 Node DAO: Fix permission service tests (setPrimaryChildrenSharedAclId needs to invalidate nodesCache) 19428: Fixed TransactionalCache issue with null and NullValueMarker 19429: Took empty cm:content creation out of FileFolderService#createImpl 19430: SAIL-234 Node DAO: Tweaks around caching and cm:auditable 19431: SAIL-4 DAO Refactor: Exception thrown when attempting writes in read-only txn have changed 19436: SAIL-234 Node DAO: Fix NPE during cm:auditable update 19475: Allow debugging of code without stepping into trivial stuff 19476: Follow-up on 19429 by ensuring CIFS/FTP set a mimetype on the ContentWriter 19477: SAIL-234 Node DAO: Leverage DAO better for NodeService.addProperties 19478: SAIL-234 NodeDAO: Added toString() for ParentAssocsInfo (cache value for parent assocs) 19479: SAIL-234 Node DAO: Fixed for parent association and property caches 19480: Made TransactionAwareSingleton bind-key a GUID 19481: SAIL-234 Node DAO: Reinstated 100K collection property tests 19482: SAIL-234 Node DAO: Node and property cache fixes highlighted by unit tests 19483: SAIL-234 Node DAO: Start on NodeBulkLoader implementation 19595: SAIL-234 Node DAO: Fix moveNode to detect cyclic relationship prior to updating ACLs for moved tree FileFolderServiceImplTest.testETHREEOH_3088_MoveIntoSelf) 20147: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19602: (RECORD ONLY) Reintegrated with HEAD up to rev 19433 19621: (RECORD ONLY) SAIL-347 19683: (RECORD ONLY) Reverse-merged 19621 for SAIL-347 19722: (RECORD ONLY) Merged /alfresco/HEAD:r19434-19721 20150: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19741: Merged DEV\V3.3-DAO-REFACTOR-2 to DEV\V3.3-DAO-REFACTOR-3 19739: Extended "move" tests 19743: Fix AuditableAspectTest.testAddAspect (to allow for node modified date tolerance) 19748: Remaining part of merge from HEAD to V3.3-DAO-REFACTOR-3 19367: Merged BRANCHES/V3.2 to HEAD: 19286: Fix for ALF-626 "Using 'null' as an authority argument in clearPermissions() cause a java.lang.NullPointerException" 19755: SAIL-234 Node DAO: Fix RepoAdminServiceImplTest.testConcurrentDynamicModelDelete (handle InvalidNodeRefException after getChildAssocs) 20692: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-4 to BRANCHES/DEV/V3.3-DAO-REFACTOR-5: - Retired all 1.3 and 1.4 upgrade scripts ... R.I.P. - Fixed CRC patch for Authorities (only tested on MySQL) - Fixed SQL patch revision numbers and bumped version schema number up 20158: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 19773: SQL mappings and scripts: SAIL-310, SAIL-304, SAIL-303 and SAIL-347 19774: Futher fix for SAIL-310: Sequence patch must take into account sequences created for 3.3 19851: SAIL-371 (SAIL-294) NodeDAO fallout: Fix QName and Namespace read/write handling and bean name in unit test 20183: Merged DAO3 to DAO4 19852: SAIL-370: Remove LinkValidation 19853: SAIL-239 (SAIL-294) Attributes.hbm.xml: Added ability to attach arbitrary property to unique context 19857: SAIL-373 Fallout from Permissions DAO refactor (SAIL-235) 19864: SAIL-239 (SAIL-294): Removed AttributeService RMI API 19865: More SAIL-239 (SAIL-294): Removed AttributeService RMI API 20208: DAO-refactor implementation of ALF-2712 query improvements 20209: Merged BRANCHES/DEV/V3.3-DAO-REFACTOR-3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 20060: Removal of AttributeService for SAIL-239 (SAIL-294) 20348: SAIL-371 (SAIL-294): Protect collection properties during map insert and retrieval 20547: SAIL-371 (SAIL-294) Attributes.hbm.xml: implement getAttributes + fixes 20573: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests and other fallout 20597: SAIL-239 Attributes.hbm.xml: WCM/AVM locking test fixes (wip) 20598: SAIL-239 Attributes.hbm.xml: WCM/AVM locking test fixes (wip) - fix AssetServiceImplTest.testSimpleLockFile NPE 20600: Fix PropertyValueDAOTest.testPropertyValue_Enum (follow-on to r20060 for SAIL-239 - which introduces ENUM prop vals) 20601: Fix UsageDAOTest.testCreateAndDeleteUsageDeltas NPE (would also affect ContentStoreCleanerScalabilityRunner) 20603: Fix CMISPropertyServiceTest.* (fallout from r20146 <- r19429 <- Took empty cm:content creation out of FileFolderService#createImpl) 20604: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - TransferServiceImplTest.* 20618: SAIL-371 (SAIL-294): NodeDAO: AuditableAspectTest (fix testCreateNodeWithAuditableProperties_ALF_2565 + add remove aspect test) 20624: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - UserUsageTest.* 20626: Fixed random keys for RuleTrigger NodeRef tracking 20635: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - PersonTest.testSplitDuplicates 20642: SAIL-371 (SAIL-294) DAO: Fixed CacheTest 20643: Removed must of the 'distribute' target's dependencies. Not for HEAD 20645: Follow-on to r20643 (Removed most of the 'distribute' target's dependencies. Not for HEAD) 20654: SAIL-371 (SAIL-294): NodeDAO: DMDeploymentTargetTest.* (do not try to remove mandatory aspects) 20655: SAIL-371 (SAIL-294): NodeDAO: Initial fix for TaggingServiceImplTest.testTagScopeUpdateViaNodePolicies (+ minor test cleanup) 20657: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - VersionMigratorTest.testMigrateOneVersion (cm:accessed not returned if null) 20658: Merged (back merge only - no merge info) BRANCHES/V3.3 to BRANCHES/DEV/V3.3-DAO-REFACTOR-4: 20090: Dynamic models: minor improvements to DictionaryModelType 20554: Improvement to model delete validation (investigating intermittent failure of RepoAdminServiceImplTest.testSimpleDynamicModelViaNodeService) 20662: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - RecordsManagementAuditServiceImplTest.* (we now ignore attempt to update 'cm:modifier' prop so update 'cm:title' prop instead) 20666: SAIL-371 (SAIL-294): NodeDAO: Fix unit tests - ADMLuceneTest.* 20668: SAIL-239 (SAIL-294) - delete WCM locks + tests (follow-on to r20060) 20674: SAIL-371 (SAIL-294) NodeDAO fallout: Cleaner and additional checks for ContentStoreCleaner 20675: SAIL-371 (SAIL-294) NodeDAO fallout: Fixed handling of ContentData git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20693 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1154 lines
44 KiB
Java
1154 lines
44 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 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.repo.model.filefolder;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.Stack;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.rule.ruletrigger.RuleTrigger;
|
|
import org.alfresco.repo.search.QueryParameterDefImpl;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.model.FileExistsException;
|
|
import org.alfresco.service.cmr.model.FileFolderService;
|
|
import org.alfresco.service.cmr.model.FileFolderServiceType;
|
|
import org.alfresco.service.cmr.model.FileFolderUtil;
|
|
import org.alfresco.service.cmr.model.FileInfo;
|
|
import org.alfresco.service.cmr.model.FileNotFoundException;
|
|
import org.alfresco.service.cmr.model.SubFolderFilter;
|
|
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.CopyService;
|
|
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
|
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.search.QueryParameterDefinition;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.GUID;
|
|
import org.alfresco.util.SearchLanguageConversion;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* Implementation of the file/folder-specific service.
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
public class FileFolderServiceImpl implements FileFolderService
|
|
{
|
|
/** Shallow search for files and folders with a name pattern */
|
|
private static final String XPATH_QUERY_SHALLOW_ALL =
|
|
"./*" +
|
|
"[like(@cm:name, $cm:name, false)" +
|
|
" and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" +
|
|
" and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" +
|
|
" or subtypeOf('" + ContentModel.TYPE_LINK + "'))]";
|
|
|
|
/** Deep search for files and folders with a name pattern */
|
|
private static final String XPATH_QUERY_DEEP_ALL =
|
|
".//*" +
|
|
"[like(@cm:name, $cm:name, false)" +
|
|
" and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" +
|
|
" and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" +
|
|
" or subtypeOf('" + ContentModel.TYPE_LINK + "'))]";
|
|
|
|
/** Deep search for folders with a name pattern */
|
|
private static final String XPATH_QUERY_DEEP_FOLDERS =
|
|
".//*" +
|
|
"[like(@cm:name, $cm:name, false)" +
|
|
" and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" +
|
|
" and (subtypeOf('" + ContentModel.TYPE_FOLDER + "'))]";
|
|
|
|
/** Deep search for files with a name pattern */
|
|
private static final String XPATH_QUERY_DEEP_FILES =
|
|
".//*" +
|
|
"[like(@cm:name, $cm:name, false)" +
|
|
" and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" +
|
|
" and (subtypeOf('" + ContentModel.TYPE_CONTENT + "')" +
|
|
" or subtypeOf('" + ContentModel.TYPE_LINK + "'))]";
|
|
|
|
/** empty parameters */
|
|
private static final QueryParameterDefinition[] PARAMS_ANY_NAME = new QueryParameterDefinition[1];
|
|
|
|
private static Log logger = LogFactory.getLog(FileFolderServiceImpl.class);
|
|
|
|
private NamespaceService namespaceService;
|
|
private DictionaryService dictionaryService;
|
|
private NodeService nodeService;
|
|
private CopyService copyService;
|
|
private SearchService searchService;
|
|
private ContentService contentService;
|
|
private MimetypeService mimetypeService;
|
|
private Set<String> systemNamespaces;
|
|
|
|
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
|
|
private List<String> systemPaths;
|
|
|
|
/**
|
|
* Default constructor
|
|
*/
|
|
public FileFolderServiceImpl()
|
|
{
|
|
systemNamespaces = new HashSet<String>(5);
|
|
}
|
|
|
|
public void setNamespaceService(NamespaceService namespaceService)
|
|
{
|
|
this.namespaceService = namespaceService;
|
|
}
|
|
|
|
public void setDictionaryService(DictionaryService dictionaryService)
|
|
{
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public void setCopyService(CopyService copyService)
|
|
{
|
|
this.copyService = copyService;
|
|
}
|
|
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
public void setContentService(ContentService contentService)
|
|
{
|
|
this.contentService = contentService;
|
|
}
|
|
|
|
public void setMimetypeService(MimetypeService mimetypeService)
|
|
{
|
|
this.mimetypeService = mimetypeService;
|
|
}
|
|
|
|
/**
|
|
* Set the namespaces that should be treated as 'system' namespaces.
|
|
* <p>
|
|
* When files or folders are renamed, the association path (QName) is normally
|
|
* modified to follow the name of the node. If, however, the namespace of the
|
|
* patch QName is in this list, the association path is left alone. This allows
|
|
* parts of the application to use well-known paths even if the end-user is
|
|
* able to modify the objects <b>cm:name</b> value.
|
|
*
|
|
* @param systemNamespaces a list of system namespaces
|
|
*/
|
|
public void setSystemNamespaces(List<String> systemNamespaces)
|
|
{
|
|
this.systemNamespaces.addAll(systemNamespaces);
|
|
}
|
|
|
|
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
|
|
public void setSystemPaths(List<String> systemPaths)
|
|
{
|
|
this.systemPaths = systemPaths;
|
|
}
|
|
|
|
|
|
public void init()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Helper method to convert node reference instances to file info
|
|
*
|
|
* @param nodeRefs the node references
|
|
* @return Return a list of file info
|
|
* @throws InvalidTypeException if the node is not a valid type
|
|
*/
|
|
private List<FileInfo> toFileInfo(List<NodeRef> nodeRefs) throws InvalidTypeException
|
|
{
|
|
List<FileInfo> results = new ArrayList<FileInfo>(nodeRefs.size());
|
|
for (NodeRef nodeRef : nodeRefs)
|
|
{
|
|
if (nodeService.exists(nodeRef))
|
|
{
|
|
FileInfo fileInfo = toFileInfo(nodeRef, true);
|
|
results.add(fileInfo);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Helper method to convert a node reference instance to a file info
|
|
*/
|
|
private FileInfo toFileInfo(NodeRef nodeRef, boolean addTranslations) throws InvalidTypeException
|
|
{
|
|
// Get the file attributes
|
|
Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
|
|
// Is it a folder
|
|
QName typeQName = nodeService.getType(nodeRef);
|
|
boolean isFolder = isFolder(typeQName);
|
|
|
|
// Construct the file info and add to the results
|
|
FileInfo fileInfo = new FileInfoImpl(nodeRef, isFolder, properties);
|
|
// Done
|
|
return fileInfo;
|
|
}
|
|
|
|
/**
|
|
* Exception when the type is not a valid File or Folder type
|
|
*
|
|
* @see ContentModel#TYPE_CONTENT
|
|
* @see ContentModel#TYPE_FOLDER
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
private static class InvalidTypeException extends RuntimeException
|
|
{
|
|
private static final long serialVersionUID = -310101369475434280L;
|
|
|
|
public InvalidTypeException(String msg)
|
|
{
|
|
super(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the type for whether it is a file or folder. All invalid types
|
|
* lead to runtime exceptions.
|
|
*
|
|
* @param typeQName the type to check
|
|
* @return Returns true if the type is a valid folder type, false if it is a file.
|
|
* @throws AlfrescoRuntimeException if the type is not handled by this service
|
|
*/
|
|
private boolean isFolder(QName typeQName) throws InvalidTypeException
|
|
{
|
|
FileFolderServiceType type = getType(typeQName);
|
|
|
|
switch (type)
|
|
{
|
|
case FILE:
|
|
return false;
|
|
case FOLDER:
|
|
return true;
|
|
case SYSTEM_FOLDER:
|
|
throw new InvalidTypeException("This service should ignore type " + ContentModel.TYPE_SYSTEM_FOLDER);
|
|
case INVALID:
|
|
default:
|
|
throw new InvalidTypeException("Type is not handled by this service: " + typeQName);
|
|
}
|
|
}
|
|
|
|
public boolean exists(NodeRef nodeRef)
|
|
{
|
|
return nodeService.exists(nodeRef);
|
|
}
|
|
|
|
public FileFolderServiceType getType(QName typeQName)
|
|
{
|
|
if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_FOLDER))
|
|
{
|
|
if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER))
|
|
{
|
|
return FileFolderServiceType.SYSTEM_FOLDER;
|
|
}
|
|
return FileFolderServiceType.FOLDER;
|
|
}
|
|
else if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_CONTENT) ||
|
|
dictionaryService.isSubClass(typeQName, ContentModel.TYPE_LINK))
|
|
{
|
|
// it is a regular file
|
|
return FileFolderServiceType.FILE;
|
|
}
|
|
else
|
|
{
|
|
// unhandled type
|
|
return FileFolderServiceType.INVALID;
|
|
}
|
|
}
|
|
|
|
public List<FileInfo> list(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<NodeRef> nodeRefs = listSimple(contextNodeRef, true, true);
|
|
// convert the noderefs
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Shallow search for files and folders: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<FileInfo> listFiles(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<NodeRef> nodeRefs = listSimple(contextNodeRef, false, true);
|
|
// convert the noderefs
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Shallow search for files: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<FileInfo> listFolders(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<NodeRef> nodeRefs = listSimple(contextNodeRef, true, false);
|
|
// convert the noderefs
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Shallow search for folders: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<FileInfo> listDeepFolders(NodeRef contextNodeRef,
|
|
SubFolderFilter filter)
|
|
{
|
|
List<NodeRef> nodeRefs = listSimpleDeep(contextNodeRef, true, false, filter);
|
|
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Deep search for files: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
|
|
}
|
|
|
|
public NodeRef searchSimple(NodeRef contextNodeRef, String name)
|
|
{
|
|
NodeRef childNodeRef = nodeService.getChildByName(contextNodeRef, ContentModel.ASSOC_CONTAINS, name);
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(
|
|
"Simple name search results: \n" +
|
|
" parent: " + contextNodeRef + "\n" +
|
|
" name: " + name + "\n" +
|
|
" result: " + childNodeRef);
|
|
}
|
|
return childNodeRef;
|
|
}
|
|
|
|
/**
|
|
* @see #search(NodeRef, String, boolean, boolean, boolean)
|
|
*/
|
|
public List<FileInfo> search(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders)
|
|
{
|
|
return search(contextNodeRef, namePattern, true, true, includeSubFolders);
|
|
}
|
|
|
|
private static final String LUCENE_MULTI_CHAR_WILDCARD = "*";
|
|
|
|
/**
|
|
* Full search with all options
|
|
*/
|
|
public List<FileInfo> search(
|
|
NodeRef contextNodeRef,
|
|
String namePattern,
|
|
boolean fileSearch,
|
|
boolean folderSearch,
|
|
boolean includeSubFolders)
|
|
{
|
|
// get the raw nodeRefs
|
|
List<NodeRef> nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders);
|
|
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
// eliminate unwanted files/folders
|
|
Iterator<FileInfo> iterator = results.iterator();
|
|
while (iterator.hasNext())
|
|
{
|
|
FileInfo file = iterator.next();
|
|
if (file.isFolder() && !folderSearch)
|
|
{
|
|
iterator.remove();
|
|
}
|
|
else if (!file.isFolder() && !fileSearch)
|
|
{
|
|
iterator.remove();
|
|
}
|
|
}
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Deep search: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" pattern: " + namePattern + "\n" +
|
|
" files: " + fileSearch + "\n" +
|
|
" folders: " + folderSearch + "\n" +
|
|
" deep: " + includeSubFolders + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Performs a full search, but doesn't translate the node references into
|
|
* file info objects. This allows {@link #checkExists(NodeRef, String)} to
|
|
* bypass the retrieval of node properties.
|
|
*/
|
|
private List<NodeRef> searchInternal(
|
|
NodeRef contextNodeRef,
|
|
String namePattern,
|
|
boolean fileSearch,
|
|
boolean folderSearch,
|
|
boolean includeSubFolders)
|
|
{
|
|
// shortcut if the search is requesting nothing
|
|
if (!fileSearch && !folderSearch)
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
if (namePattern == null)
|
|
{
|
|
namePattern = LUCENE_MULTI_CHAR_WILDCARD; // default to wildcard
|
|
}
|
|
// now check if we can use Lucene to handle this query
|
|
boolean anyName = namePattern.equals(LUCENE_MULTI_CHAR_WILDCARD);
|
|
|
|
List<NodeRef> nodeRefs = null;
|
|
if (anyName)
|
|
{
|
|
// This is search for any name
|
|
if(includeSubFolders)
|
|
{
|
|
nodeRefs = listSimpleDeep(contextNodeRef, folderSearch, fileSearch, null);
|
|
}
|
|
else
|
|
{
|
|
nodeRefs = listSimple(contextNodeRef, folderSearch, fileSearch);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO - we need to get rid of this xpath stuff
|
|
// if the name pattern is null, then we use the ANY pattern
|
|
QueryParameterDefinition[] params = null;
|
|
if (namePattern != null)
|
|
{
|
|
// the interface specifies the Lucene syntax, so perform a conversion
|
|
namePattern = SearchLanguageConversion.convert(
|
|
SearchLanguageConversion.DEF_LUCENE,
|
|
SearchLanguageConversion.DEF_XPATH_LIKE,
|
|
namePattern);
|
|
|
|
params = new QueryParameterDefinition[1];
|
|
params[0] = new QueryParameterDefImpl(
|
|
ContentModel.PROP_NAME,
|
|
dictionaryService.getDataType(DataTypeDefinition.TEXT),
|
|
true,
|
|
namePattern);
|
|
}
|
|
else
|
|
{
|
|
// name pattern is null so search for ANY_NAME
|
|
params = PARAMS_ANY_NAME;
|
|
}
|
|
// determine the correct query to use
|
|
String query = null;
|
|
if (includeSubFolders)
|
|
{
|
|
// this is a deep search
|
|
if(!fileSearch && folderSearch)
|
|
{
|
|
// This is a folder search only;
|
|
query = XPATH_QUERY_DEEP_FOLDERS;
|
|
}
|
|
else if(fileSearch && !folderSearch)
|
|
{
|
|
// This is a folder search only;
|
|
query = XPATH_QUERY_DEEP_FILES;
|
|
}
|
|
else
|
|
{
|
|
query = XPATH_QUERY_DEEP_ALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is a shallow search
|
|
query = XPATH_QUERY_SHALLOW_ALL;
|
|
}
|
|
// execute the query
|
|
nodeRefs = searchService.selectNodes(
|
|
contextNodeRef,
|
|
query,
|
|
params,
|
|
namespaceService,
|
|
false);
|
|
}
|
|
// done
|
|
return nodeRefs;
|
|
}
|
|
|
|
private List<NodeRef> listSimple(NodeRef contextNodeRef, boolean folders, boolean files)
|
|
{
|
|
Set<QName> searchTypeQNames = new HashSet<QName>(10);
|
|
// Build a list of file and folder types
|
|
if (folders)
|
|
{
|
|
Collection<QName> qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true);
|
|
searchTypeQNames.addAll(qnames);
|
|
searchTypeQNames.add(ContentModel.TYPE_FOLDER);
|
|
}
|
|
if (files)
|
|
{
|
|
Collection<QName> qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true);
|
|
searchTypeQNames.addAll(qnames);
|
|
searchTypeQNames.add(ContentModel.TYPE_CONTENT);
|
|
qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true);
|
|
searchTypeQNames.addAll(qnames);
|
|
searchTypeQNames.add(ContentModel.TYPE_LINK);
|
|
}
|
|
// Remove 'system' folders
|
|
Collection<QName> qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true);
|
|
searchTypeQNames.removeAll(qnames);
|
|
searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER);
|
|
// Shortcut
|
|
if (searchTypeQNames.size() == 0)
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
// Do the query
|
|
List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(contextNodeRef, searchTypeQNames);
|
|
List<NodeRef> result = new ArrayList<NodeRef>(childAssocRefs.size());
|
|
for (ChildAssociationRef assocRef : childAssocRefs)
|
|
{
|
|
result.add(assocRef.getChildRef());
|
|
}
|
|
// Done
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A deep version of listSimple. Which recursively walks down the tree from a given starting point, returning
|
|
* the node refs of files or folders found along the way.
|
|
*
|
|
* MER: I've added this rather than changing listSimple to minimise the risk of breaking
|
|
* the existing code. This is a quick performance improvement between using
|
|
* XPath which is awful or adding new methods to the NodeService/DB This is also a dangerous method in that it can return a
|
|
* lot of data and take a long time.
|
|
*
|
|
* The folder filter is called for each sub-folder to determine whether to search in that sub-folder, should a subfolder be excluded
|
|
* then all its chidren are excluded as well.
|
|
*
|
|
* @param contextNodeRef the starting point.
|
|
* @param folders return nodes of type folders.
|
|
* @param files return nodes of type files.
|
|
* @param subfolder filter controls which folders to search. If null then all subfolders are searched.
|
|
* @return list of node references
|
|
*/
|
|
private List<NodeRef> listSimpleDeep(NodeRef contextNodeRef, boolean folders, boolean files, SubFolderFilter folderFilter)
|
|
{
|
|
Set<QName> folderTypeQNames = new HashSet<QName>(10);
|
|
Set<QName> fileTypeQNames = new HashSet<QName>(10);
|
|
|
|
// To hold the results.
|
|
List<NodeRef> result = new ArrayList<NodeRef>();
|
|
|
|
// Build a list of folder types
|
|
Collection<QName> qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true);
|
|
folderTypeQNames.addAll(qnames);
|
|
folderTypeQNames.add(ContentModel.TYPE_FOLDER);
|
|
|
|
// Remove 'system' folders and all descendants
|
|
Collection<QName> systemFolderQNames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true);
|
|
folderTypeQNames.removeAll(systemFolderQNames);
|
|
folderTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER);
|
|
|
|
if (files)
|
|
{
|
|
Collection<QName> fileQNames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true);
|
|
fileTypeQNames.addAll(fileQNames);
|
|
fileTypeQNames.add(ContentModel.TYPE_CONTENT);
|
|
Collection<QName> linkQNames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true);
|
|
fileTypeQNames.addAll(linkQNames);
|
|
fileTypeQNames.add(ContentModel.TYPE_LINK);
|
|
}
|
|
|
|
if(!folders && !files)
|
|
{
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
// Shortcut
|
|
if (folderTypeQNames.size() == 0)
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
Stack<NodeRef> toSearch = new Stack<NodeRef>();
|
|
toSearch.push(contextNodeRef);
|
|
|
|
// Now we need to walk down the folders.
|
|
while(!toSearch.empty())
|
|
{
|
|
NodeRef currentDir = toSearch.pop();
|
|
|
|
List<ChildAssociationRef> folderAssocRefs = nodeService.getChildAssocs(currentDir, folderTypeQNames);
|
|
|
|
for (ChildAssociationRef folderRef : folderAssocRefs)
|
|
{
|
|
// We have some child folders
|
|
boolean include = true;
|
|
if(folderFilter != null)
|
|
{
|
|
include = folderFilter.isEnterSubfolder(folderRef);
|
|
if(include)
|
|
{
|
|
// yes search in these subfolders
|
|
toSearch.push(folderRef.getChildRef());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No filter - Add the folders in the currentDir
|
|
toSearch.push(folderRef.getChildRef());
|
|
}
|
|
|
|
if(folders && include)
|
|
{
|
|
result.add(folderRef.getChildRef());
|
|
}
|
|
}
|
|
|
|
if(files)
|
|
{
|
|
// Add the files in the current dir
|
|
List<ChildAssociationRef> fileAssocRefs = nodeService.getChildAssocs(currentDir, fileTypeQNames);
|
|
for (ChildAssociationRef fileRef : fileAssocRefs)
|
|
{
|
|
result.add(fileRef.getChildRef());
|
|
}
|
|
}
|
|
}
|
|
|
|
logger.debug("search deep finished size:" + result.size());
|
|
|
|
// Done
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @see #move(NodeRef, NodeRef, String)
|
|
*/
|
|
public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException
|
|
{
|
|
// NOTE:
|
|
//
|
|
// This information is placed in the transaction to indicate that a rename has taken place. This information is
|
|
// used by the rule trigger to ensure inbound rule is not triggered by a file rename
|
|
//
|
|
// See http://issues.alfresco.com/browse/AR-1544
|
|
Set<String> nodeRefRenameSet = TransactionalResourceHelper.getSet(RuleTrigger.RULE_TRIGGER_NODESET);
|
|
String marker = sourceNodeRef.toString()+"rename";
|
|
nodeRefRenameSet.add(marker);
|
|
|
|
return moveOrCopy(sourceNodeRef, null, newName, true);
|
|
}
|
|
|
|
/**
|
|
* @see #moveOrCopy(NodeRef, NodeRef, String, boolean)
|
|
*/
|
|
public FileInfo move(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException
|
|
{
|
|
return moveOrCopy(sourceNodeRef, targetParentRef, newName, true);
|
|
}
|
|
|
|
/**
|
|
* @see #moveOrCopy(NodeRef, NodeRef, String, boolean)
|
|
*/
|
|
public FileInfo copy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException
|
|
{
|
|
return moveOrCopy(sourceNodeRef, targetParentRef, newName, false);
|
|
}
|
|
|
|
/**
|
|
* Implements both move and copy behaviour
|
|
*
|
|
* @param move true to move, otherwise false to copy
|
|
*/
|
|
private FileInfo moveOrCopy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName, boolean move) throws FileExistsException, FileNotFoundException
|
|
{
|
|
// get file/folder in its current state
|
|
FileInfo beforeFileInfo = toFileInfo(sourceNodeRef, true);
|
|
// check the name - null means keep the existing name
|
|
if (newName == null)
|
|
{
|
|
newName = beforeFileInfo.getName();
|
|
}
|
|
|
|
boolean nameChanged = (newName.equals(beforeFileInfo.getName()) == false);
|
|
|
|
// we need the current association type
|
|
ChildAssociationRef assocRef = nodeService.getPrimaryParent(sourceNodeRef);
|
|
if (targetParentRef == null)
|
|
{
|
|
targetParentRef = assocRef.getParentRef();
|
|
}
|
|
|
|
boolean changedParent = !targetParentRef.equals(assocRef.getParentRef());
|
|
// there is nothing to do if both the name and parent folder haven't changed
|
|
if (!nameChanged && !changedParent)
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Doing nothing - neither filename or parent has changed: \n" +
|
|
" parent: " + targetParentRef + "\n" +
|
|
" before: " + beforeFileInfo + "\n" +
|
|
" new name: " + newName);
|
|
}
|
|
return beforeFileInfo;
|
|
}
|
|
|
|
QName existingQName = assocRef.getQName();
|
|
QName qname;
|
|
if (nameChanged && !systemNamespaces.contains(existingQName.getNamespaceURI()))
|
|
{
|
|
// Change the localname to match the new name
|
|
qname = QName.createQName(
|
|
assocRef.getQName().getNamespaceURI(),
|
|
QName.createValidLocalName(newName));
|
|
}
|
|
else
|
|
{
|
|
// Keep the localname
|
|
qname = existingQName;
|
|
}
|
|
|
|
QName targetParentType = nodeService.getType(targetParentRef);
|
|
|
|
// Fix AWC-1517
|
|
QName assocTypeQname = null;
|
|
if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_FOLDER))
|
|
{
|
|
assocTypeQname = ContentModel.ASSOC_CONTAINS; // cm:folder -> cm:contains
|
|
}
|
|
else if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_CONTAINER))
|
|
{
|
|
assocTypeQname = ContentModel.ASSOC_CHILDREN; // sys:container -> sys:children
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidTypeException("Unexpected type (" + targetParentType + ") for target parent: " + targetParentRef);
|
|
}
|
|
|
|
// move or copy
|
|
NodeRef targetNodeRef = null;
|
|
if (move)
|
|
{
|
|
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
|
|
if (!isSystemPath(sourceNodeRef))
|
|
{
|
|
// The cm:name might clash with another node in the target location.
|
|
if (nameChanged)
|
|
{
|
|
// The name will be changing, so we really need to set the node's name to the new
|
|
// name. This can't be done at the same time as the move - to avoid incorrect violations
|
|
// of the name constraints, the cm:name is set to something random and will be reset
|
|
// to the correct name later.
|
|
nodeService.setProperty(sourceNodeRef, ContentModel.PROP_NAME, GUID.generate());
|
|
}
|
|
try
|
|
{
|
|
// move the node so that the association moves as well
|
|
ChildAssociationRef newAssocRef = nodeService.moveNode(
|
|
sourceNodeRef,
|
|
targetParentRef,
|
|
assocTypeQname,
|
|
qname);
|
|
targetNodeRef = newAssocRef.getChildRef();
|
|
}
|
|
catch (DuplicateChildNodeNameException e)
|
|
{
|
|
throw new FileExistsException(targetParentRef, newName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// system path folders do not need to be moved
|
|
targetNodeRef = sourceNodeRef;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
// Copy the node. The cm:name will be dropped and reset later.
|
|
targetNodeRef = copyService.copy(
|
|
sourceNodeRef,
|
|
targetParentRef,
|
|
assocTypeQname,
|
|
qname,
|
|
true);
|
|
}
|
|
catch (DuplicateChildNodeNameException e)
|
|
{
|
|
throw new FileExistsException(targetParentRef, newName);
|
|
}
|
|
}
|
|
|
|
// Only update the name if it has changed
|
|
String currentName = (String)nodeService.getProperty(targetNodeRef, ContentModel.PROP_NAME);
|
|
if (currentName.equals(newName) == false)
|
|
{
|
|
try
|
|
{
|
|
// changed the name property
|
|
nodeService.setProperty(targetNodeRef, ContentModel.PROP_NAME, newName);
|
|
|
|
// May need to update the mimetype, to support apps using .tmp files when saving
|
|
ContentData contentData = (ContentData)nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT);
|
|
|
|
// Check the newName and oldName extensions.
|
|
// Keep previous mimetype if
|
|
// 1. new extension is empty
|
|
// 2. new extension is '.tmp'
|
|
// 3. extension was not changed,
|
|
//
|
|
// It fixes the ETWOTWO-16 issue.
|
|
String oldExt = getExtension(beforeFileInfo.getName());
|
|
String newExt = getExtension(newName);
|
|
if (contentData != null &&
|
|
newExt.length() != 0 &&
|
|
!"tmp".equalsIgnoreCase(newExt) &&
|
|
!newExt.equalsIgnoreCase(oldExt))
|
|
{
|
|
String targetMimetype = contentData.getMimetype();
|
|
String newMimetype = mimetypeService.guessMimetype(newName);
|
|
if (!targetMimetype.equalsIgnoreCase(newMimetype))
|
|
{
|
|
contentData = ContentData.setMimetype(contentData, newMimetype);
|
|
nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, contentData);
|
|
}
|
|
}
|
|
}
|
|
catch (DuplicateChildNodeNameException e)
|
|
{
|
|
throw new FileExistsException(targetParentRef, newName);
|
|
}
|
|
}
|
|
|
|
// get the details after the operation
|
|
FileInfo afterFileInfo = toFileInfo(targetNodeRef, true);
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("" + (move ? "Moved" : "Copied") + " node: \n" +
|
|
" parent: " + targetParentRef + "\n" +
|
|
" before: " + beforeFileInfo + "\n" +
|
|
" after: " + afterFileInfo);
|
|
}
|
|
return afterFileInfo;
|
|
}
|
|
|
|
/**
|
|
* Determine if the specified node is a special "system" folder path based node
|
|
*
|
|
* TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
|
|
*
|
|
* @param nodeRef node to check
|
|
* @return true => system folder path based node
|
|
*/
|
|
private boolean isSystemPath(NodeRef nodeRef)
|
|
{
|
|
Path path = nodeService.getPath(nodeRef);
|
|
String prefixedPath = path.toPrefixString(namespaceService);
|
|
return systemPaths.contains(prefixedPath);
|
|
}
|
|
|
|
public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException
|
|
{
|
|
return createImpl(parentNodeRef, name, typeQName, null);
|
|
}
|
|
|
|
public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException
|
|
{
|
|
return createImpl(parentNodeRef, name, typeQName, assocQName);
|
|
}
|
|
|
|
private FileInfo createImpl(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException
|
|
{
|
|
// set up initial properties
|
|
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(11);
|
|
properties.put(ContentModel.PROP_NAME, (Serializable) name);
|
|
|
|
// create the node
|
|
if (assocQName == null)
|
|
{
|
|
assocQName = QName.createQName(
|
|
NamespaceService.CONTENT_MODEL_1_0_URI,
|
|
QName.createValidLocalName(name));
|
|
}
|
|
ChildAssociationRef assocRef = null;
|
|
try
|
|
{
|
|
assocRef = nodeService.createNode(
|
|
parentNodeRef,
|
|
ContentModel.ASSOC_CONTAINS,
|
|
assocQName,
|
|
typeQName,
|
|
properties);
|
|
}
|
|
catch (DuplicateChildNodeNameException e)
|
|
{
|
|
throw new FileExistsException(parentNodeRef, name);
|
|
}
|
|
|
|
NodeRef nodeRef = assocRef.getChildRef();
|
|
FileInfo fileInfo = toFileInfo(nodeRef, true);
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Created: \n" +
|
|
" parent: " + parentNodeRef + "\n" +
|
|
" created: " + fileInfo);
|
|
}
|
|
return fileInfo;
|
|
}
|
|
|
|
public void delete(NodeRef nodeRef)
|
|
{
|
|
nodeService.deleteNode(nodeRef);
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Deleted: \n" +
|
|
" node: " + nodeRef);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks for the presence of, and creates as necessary, the folder structure in the provided path.
|
|
* <p>
|
|
* An empty path list is not allowed as it would be impossible to necessarily return file info
|
|
* for the parent node - it might not be a folder node.
|
|
* @param parentNodeRef the node under which the path will be created
|
|
* @param pathElements the folder name path to create - may not be empty
|
|
* @param folderTypeQName the types of nodes to create. This must be a valid subtype of
|
|
* {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}.
|
|
* @return Returns the info of the last folder in the path.
|
|
* @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class.
|
|
*/
|
|
public FileInfo makeFolders(NodeRef parentNodeRef, List<String> pathElements, QName folderTypeQName)
|
|
{
|
|
return FileFolderUtil.makeFolders(this, parentNodeRef, pathElements, folderTypeQName);
|
|
}
|
|
|
|
/**
|
|
* Checks for the presence of, and creates as necessary, the folder structure in the provided path.
|
|
* <p>
|
|
* An empty path list is not allowed as it would be impossible to necessarily return file info
|
|
* for the parent node - it might not be a folder node.
|
|
* @param parentNodeRef the node under which the path will be created
|
|
* @param pathElements the folder name path to create - may not be empty
|
|
* @param folderTypeQName the types of nodes to create. This must be a valid subtype of
|
|
* {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}.
|
|
* @return Returns the info of the last folder in the path.
|
|
* @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class.
|
|
*/
|
|
public static FileInfo makeFolders(FileFolderService service, NodeRef parentNodeRef, List<String> pathElements, QName folderTypeQName)
|
|
{
|
|
return FileFolderUtil.makeFolders(service, parentNodeRef, pathElements, folderTypeQName);
|
|
}
|
|
|
|
public List<FileInfo> getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException
|
|
{
|
|
// check the root
|
|
if (rootNodeRef == null)
|
|
{
|
|
rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef());
|
|
}
|
|
try
|
|
{
|
|
List<FileInfo> results = new ArrayList<FileInfo>(10);
|
|
// get the primary path
|
|
Path path = nodeService.getPath(nodeRef);
|
|
// iterate and turn the results into file info objects
|
|
boolean foundRoot = false;
|
|
for (Path.Element element : path)
|
|
{
|
|
// ignore everything down to the root
|
|
Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element;
|
|
NodeRef childNodeRef = assocElement.getRef().getChildRef();
|
|
if (childNodeRef.equals(rootNodeRef))
|
|
{
|
|
// just found the root - but we don't put in an entry for it
|
|
foundRoot = true;
|
|
continue;
|
|
}
|
|
else if (!foundRoot)
|
|
{
|
|
// keep looking for the root
|
|
continue;
|
|
}
|
|
// we found the root and expect to be building the path up
|
|
FileInfo pathInfo = toFileInfo(childNodeRef, true);
|
|
results.add(pathInfo);
|
|
}
|
|
// check that we found the root
|
|
if (!foundRoot || results.size() == 0)
|
|
{
|
|
throw new FileNotFoundException(nodeRef);
|
|
}
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Built name path for node: \n" +
|
|
" root: " + rootNodeRef + "\n" +
|
|
" node: " + nodeRef + "\n" +
|
|
" path: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
catch (InvalidNodeRefException e)
|
|
{
|
|
throw new FileNotFoundException(nodeRef);
|
|
}
|
|
}
|
|
|
|
public FileInfo resolveNamePath(NodeRef rootNodeRef, List<String> pathElements) throws FileNotFoundException
|
|
{
|
|
if (pathElements.size() == 0)
|
|
{
|
|
throw new IllegalArgumentException("Path elements list is empty");
|
|
}
|
|
// walk the folder tree first
|
|
NodeRef parentNodeRef = rootNodeRef;
|
|
StringBuilder currentPath = new StringBuilder(pathElements.size() << 4);
|
|
int folderCount = pathElements.size() - 1;
|
|
for (int i = 0; i < folderCount; i++)
|
|
{
|
|
String pathElement = pathElements.get(i);
|
|
NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement);
|
|
if (folderNodeRef == null)
|
|
{
|
|
StringBuilder sb = new StringBuilder(128);
|
|
sb.append("Folder not found: " + currentPath);
|
|
throw new FileNotFoundException(sb.toString());
|
|
}
|
|
parentNodeRef = folderNodeRef;
|
|
}
|
|
// we have resolved the folder path - resolve the last component
|
|
String pathElement = pathElements.get(pathElements.size() - 1);
|
|
NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement);
|
|
if (fileNodeRef == null)
|
|
{
|
|
StringBuilder sb = new StringBuilder(128);
|
|
sb.append("File not found: " + currentPath);
|
|
throw new FileNotFoundException(sb.toString());
|
|
}
|
|
FileInfo result = getFileInfo(fileNodeRef);
|
|
// found it
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Resoved path element: \n" +
|
|
" root: " + rootNodeRef + "\n" +
|
|
" path: " + currentPath + "\n" +
|
|
" node: " + result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public FileInfo getFileInfo(NodeRef nodeRef)
|
|
{
|
|
try
|
|
{
|
|
return toFileInfo(nodeRef, true);
|
|
}
|
|
catch (InvalidTypeException e)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public ContentReader getReader(NodeRef nodeRef)
|
|
{
|
|
FileInfo fileInfo = toFileInfo(nodeRef, false);
|
|
if (fileInfo.isFolder())
|
|
{
|
|
throw new InvalidTypeException("Unable to get a content reader for a folder: " + fileInfo);
|
|
}
|
|
return contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
|
}
|
|
|
|
public ContentWriter getWriter(NodeRef nodeRef)
|
|
{
|
|
FileInfo fileInfo = toFileInfo(nodeRef, false);
|
|
if (fileInfo.isFolder())
|
|
{
|
|
throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo);
|
|
}
|
|
return contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
|
|
}
|
|
|
|
private String getExtension(String name)
|
|
{
|
|
String result = "";
|
|
if (name != null)
|
|
{
|
|
name = name.trim();
|
|
int index = name.lastIndexOf('.');
|
|
if (index > -1 && (index < name.length() - 1))
|
|
{
|
|
result = name.substring(index + 1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|