mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-22 15:12:38 +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;
 | |
|     }
 | |
| }
 |