mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	125515 slanglois: MNT-16155 Update source headers - add new Copyrights for Java and JSP source files + automatic check in the build git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.1.N/root@125606 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
			
				
	
	
		
			343 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * #%L
 | |
|  * Alfresco Repository
 | |
|  * %%
 | |
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | |
|  * %%
 | |
|  * This file is part of the Alfresco software. 
 | |
|  * If the software was purchased under a paid Alfresco license, the terms of 
 | |
|  * the paid license agreement will prevail.  Otherwise, the software is 
 | |
|  * provided under the following open source license terms:
 | |
|  * 
 | |
|  * 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/>.
 | |
|  * #L%
 | |
|  */
 | |
| package org.alfresco.repo.model.filefolder;
 | |
| 
 | |
| import java.util.Arrays;
 | |
| import java.util.List;
 | |
| import java.util.Locale;
 | |
| import java.util.UUID;
 | |
| import java.util.concurrent.atomic.AtomicInteger;
 | |
| 
 | |
| import org.alfresco.model.ContentModel;
 | |
| import org.alfresco.repo.admin.RepositoryState;
 | |
| import org.alfresco.repo.content.MimetypeMap;
 | |
| import org.alfresco.repo.content.filestore.SpoofedTextContentReader;
 | |
| import org.alfresco.repo.model.Repository;
 | |
| import org.alfresco.repo.node.MLPropertyInterceptor;
 | |
| import org.alfresco.repo.policy.BehaviourFilter;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | |
| import org.alfresco.service.cmr.model.FileFolderService;
 | |
| import org.alfresco.service.cmr.model.FileInfo;
 | |
| import org.alfresco.service.cmr.model.FileNotFoundException;
 | |
| import org.alfresco.service.cmr.repository.ContentData;
 | |
| import org.alfresco.service.cmr.repository.ContentService;
 | |
| import org.alfresco.service.cmr.repository.ContentWriter;
 | |
| import org.alfresco.service.cmr.repository.MLText;
 | |
| import org.alfresco.service.cmr.repository.NodeRef;
 | |
| import org.alfresco.service.cmr.repository.NodeService;
 | |
| import org.alfresco.service.cmr.security.PermissionService;
 | |
| import org.alfresco.service.transaction.TransactionService;
 | |
| import org.alfresco.util.random.NormalDistributionHelper;
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| 
 | |
| /**
 | |
|  * Class to aid in the generation of file-folder data structures for load test purposes.
 | |
|  * <p/>
 | |
|  * All paths referenced are in relation to the standard Alfresco "Company Home" folder,
 | |
|  * which acts as the root for accessing documents and folders via many APIs.
 | |
|  * <p/>
 | |
|  * <strong>WARNING:  This class may be used but will probably NOT be considered part of the public API i.e.
 | |
|  * will probably change in line with Alfresco's internal requirements; nevertheless, backward
 | |
|  * compatibility will be maintained where practical.</strong>
 | |
|  * <p/>
 | |
|  * Timestamp propagation to the containing folder is disabled in order to reduce overhead.
 | |
|  * 
 | |
|  * @author Derek Hulley
 | |
|  * @since 5.1
 | |
|  */
 | |
| public class FileFolderLoader
 | |
| {
 | |
|     private static Log logger = LogFactory.getLog(FileFolderLoader.class);
 | |
|     
 | |
|     private final RepositoryState repoState;
 | |
|     private final TransactionService transactionService;
 | |
|     private final Repository repositoryHelper;
 | |
|     private final FileFolderService fileFolderService;
 | |
|     private final NodeService nodeService;
 | |
|     private final ContentService contentService;
 | |
|     private final BehaviourFilter policyBehaviourFilter;
 | |
|     private final NormalDistributionHelper normalDistribution;
 | |
|     
 | |
|     /**
 | |
|      * @param repoState             keep track of repository readiness
 | |
|      * @param transactionService    ensure proper rollback, where required
 | |
|      * @param repositoryHelper      access standard repository paths
 | |
|      * @param fileFolderService     perform actual file-folder manipulation
 | |
|      */
 | |
|     public FileFolderLoader(
 | |
|             RepositoryState repoState,
 | |
|             TransactionService transactionService,
 | |
|             Repository repositoryHelper,
 | |
|             FileFolderService fileFolderService,
 | |
|             NodeService nodeService,
 | |
|             ContentService contentService,
 | |
|             BehaviourFilter policyBehaviourFilter)
 | |
|     {
 | |
|         this.repoState = repoState;
 | |
|         this.transactionService = transactionService;
 | |
|         this.repositoryHelper = repositoryHelper;
 | |
|         this.fileFolderService = fileFolderService;
 | |
|         this.nodeService = nodeService;
 | |
|         this.contentService = contentService;
 | |
|         this.policyBehaviourFilter = policyBehaviourFilter;
 | |
|         
 | |
|         this.normalDistribution = new NormalDistributionHelper();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return                      the helper for accessing common repository paths
 | |
|      */
 | |
|     public Repository getRepository()
 | |
|     {
 | |
|         return repositoryHelper;
 | |
|     }
 | |
|     
 | |
|     /** <p>
 | |
|      * Attempt to create a given number of text files within a specified folder.  The load tolerates failures unless these
 | |
|      * prevent <b>any</b> files from being created.  Options exist to control the file size and text content distributions.
 | |
|      * The <b>cm:auditable</b> aspect automatically applied to each node as part of Alfresco.
 | |
|      * Additionally, extra residual text properties can be added in order to increase the size of the database storage.</p>
 | |
|      * <p>
 | |
|      * The files are created regardless of the read-write state of the server.</p>
 | |
|      * <p>
 | |
|      * The current thread's authentication determines the user context and the authenticated user has to have sufficient
 | |
|      * permissions to {@link PermissionService#CREATE_CHILDREN create children} within the folder.  This will be enforced
 | |
|      * by the {@link FileFolderService}.</p>
 | |
|      * 
 | |
|      * @param folderPath                        the full path to the folder within the context of the
 | |
|      *                                          {@link Repository#getCompanyHome() Alfresco Company Home} folder e.g.
 | |
|      *                                          <pre>/Sites/Site.default.00009/documentLibrary</pre>.
 | |
|      * @param fileCount                         the number of files to create
 | |
|      * @param filesPerTxn                       the number of files to create in a transaction.  Any failures within a
 | |
|      *                                          transaction (batch) will force the transaction to rollback; normal
 | |
|      *                                          {@link RetryingTransactionHelper#doInTransaction(org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback) retrying semantics }
 | |
|      *                                          are employed.
 | |
|      * @param minFileSize                       the smallest file size (all sizes within 1 standard deviation of the mean)
 | |
|      * @param maxFileSize                       the largest file size (all sizes within 1 standard deviation of the mean)
 | |
|      * @param maxUniqueDocuments                the maximum number of unique documents that should be created globally.
 | |
|      *                                          A value of <tt>1</tt> means that all documents will be the same document;
 | |
|      *                                          <tt>10,000,000</tt> will mean that there will be 10M unique text sequences used.
 | |
|      * @param forceBinaryStorage                <tt>true</tt> to actually write the spoofed text data to the binary store
 | |
|      *                                          i.e. the physical underlying storage will contain the binary data, allowing
 | |
|      *                                          IO to be realistically stressed if that is a requirement.  To save disk
 | |
|      *                                          space, set this value to <tt>false</tt>, which will see all file data get
 | |
|      *                                          generated on request using a repeatable algorithm.
 | |
|      * @param descriptionCount                  the number of <b>cm:description</b> multilingual entries to create.  The current locale
 | |
|      *                                          is used for the first entry and additional locales are added using the
 | |
|      *                                          {@link Locale#getISOLanguages() Java basic languages list}.  The total count cannot
 | |
|      *                                          exceed the total number of languages available.
 | |
|      *                                          TODO: Note that the actual text stored is not (yet) localized.
 | |
|      * @param descriptionSize                   the size (in bytes) for each <b>cm:description</b> property created; values from 16 bytes to 1024 bytes are supported
 | |
|      * @return                                  the number of files successfully created
 | |
|      * @throws FileNotFoundException            if the folder path does not exist
 | |
|      * @throws IllegalStateException            if the repository is not ready
 | |
|      */
 | |
|     public int createFiles(
 | |
|             final String folderPath,
 | |
|             final int fileCount,
 | |
|             final int filesPerTxn,
 | |
|             final long minFileSize, long maxFileSize,
 | |
|             final long maxUniqueDocuments,
 | |
|             final boolean forceBinaryStorage,
 | |
|             final int descriptionCount, final long descriptionSize
 | |
|             ) throws FileNotFoundException
 | |
|     {
 | |
|         if (repoState.isBootstrapping())
 | |
|         {
 | |
|             throw new IllegalStateException("Repository is still bootstrapping.");
 | |
|         }
 | |
|         if (minFileSize > maxFileSize)
 | |
|         {
 | |
|             throw new IllegalArgumentException("Min/max file sizes incorrect: " + minFileSize + "-" + maxFileSize);
 | |
|         }
 | |
|         if (filesPerTxn < 1)
 | |
|         {
 | |
|             throw new IllegalArgumentException("'filesPerTxn' must be 1 or more.");
 | |
|         }
 | |
|         if (descriptionCount < 0 || descriptionCount > Locale.getISOLanguages().length)
 | |
|         {
 | |
|             throw new IllegalArgumentException("'descriptionCount' exceeds the number of languages available.");
 | |
|         }
 | |
|         if (descriptionSize < 16L || descriptionSize > 1024L)
 | |
|         {
 | |
|             throw new IllegalArgumentException("'descriptionSize' can be anything from 16 to 1024 bytes.");
 | |
|         }
 | |
|         
 | |
|         RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
 | |
|         // Locate the folder; this MUST work
 | |
|         RetryingTransactionCallback<NodeRef> findFolderWork = new RetryingTransactionCallback<NodeRef>()
 | |
|         {
 | |
|             @Override
 | |
|             public NodeRef execute() throws Throwable
 | |
|             {
 | |
|                 String folderPathFixed = folderPath;
 | |
|                 // Homogenise the path
 | |
|                 if (!folderPath.startsWith("/"))
 | |
|                 {
 | |
|                     folderPathFixed = "/" + folderPath;
 | |
|                 }
 | |
|                 NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome();
 | |
|                 // Special case for the root
 | |
|                 if (folderPath.equals("/"))
 | |
|                 {
 | |
|                     return companyHomeNodeRef;
 | |
|                 }
 | |
|                 List<String> folderPathElements = Arrays.asList(folderPathFixed.substring(1).split("/"));
 | |
|                 FileInfo folderInfo = fileFolderService.resolveNamePath(companyHomeNodeRef, folderPathElements, true);
 | |
|                 // Done
 | |
|                 return folderInfo.getNodeRef();
 | |
|             }
 | |
|         };
 | |
|         NodeRef folderNodeRef = txnHelper.doInTransaction(findFolderWork, false, true);
 | |
|         // Create files
 | |
|         int created = createFiles(
 | |
|                 folderNodeRef, fileCount, filesPerTxn, minFileSize, maxFileSize, maxUniqueDocuments, forceBinaryStorage,
 | |
|                 descriptionCount, descriptionSize);
 | |
|         // Done
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Created " + created + " files in folder " + folderPath);
 | |
|         }
 | |
|         return created;
 | |
|     }
 | |
|     
 | |
|     private int createFiles(
 | |
|             final NodeRef folderNodeRef,
 | |
|             final int fileCount,
 | |
|             final int filesPerTxn,
 | |
|             final long minFileSize, final long maxFileSize,
 | |
|             final long maxUniqueDocuments,
 | |
|             final boolean forceBinaryStorage,
 | |
|             final int descriptionCount, final long descriptionSize)
 | |
|     {
 | |
|         final String nameBase = UUID.randomUUID().toString();
 | |
|         
 | |
|         final AtomicInteger count = new AtomicInteger(0);
 | |
|         RetryingTransactionCallback<Void> createFilesWork = new RetryingTransactionCallback<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void execute() throws Throwable
 | |
|             {
 | |
|                 // Disable timestamp propagation to the parent by disabling cm:auditable
 | |
|                 policyBehaviourFilter.disableBehaviour(folderNodeRef, ContentModel.ASPECT_AUDITABLE);
 | |
|                 
 | |
|                 for (int i = 0; i < filesPerTxn; i++)
 | |
|                 {
 | |
|                     // Only create files while we need; we may need to do fewer in the last txn
 | |
|                     if (count.get() >= fileCount)
 | |
|                     {
 | |
|                         break;
 | |
|                     }
 | |
|                     // Each load has it's own base name
 | |
|                     String name = String.format("%s-%6d.txt", nameBase, count.get());
 | |
|                     // Create a file
 | |
|                     FileInfo fileInfo = fileFolderService.create(
 | |
|                             folderNodeRef,
 | |
|                             name,
 | |
|                             ContentModel.TYPE_CONTENT, ContentModel.ASSOC_CONTAINS);
 | |
|                     NodeRef fileNodeRef = fileInfo.getNodeRef();
 | |
|                     // Spoofed document
 | |
|                     Locale locale = Locale.ENGLISH;
 | |
|                     long seed = (long) (Math.random() * maxUniqueDocuments);
 | |
|                     long size = normalDistribution.getValue(minFileSize, maxFileSize);
 | |
|                     String contentUrl = SpoofedTextContentReader.createContentUrl(locale, seed, size);
 | |
|                     SpoofedTextContentReader reader = new SpoofedTextContentReader(contentUrl);
 | |
|                     if (forceBinaryStorage)
 | |
|                     {
 | |
|                         // Stream the text into the real storage
 | |
|                         ContentWriter writer = contentService.getWriter(fileNodeRef, ContentModel.PROP_CONTENT, true);
 | |
|                         writer.setEncoding("UTF-8");
 | |
|                         writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
 | |
|                         writer.putContent(reader);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         // Just use the URL
 | |
|                         ContentData contentData = reader.getContentData();
 | |
|                         nodeService.setProperty(fileNodeRef, ContentModel.PROP_CONTENT, contentData);
 | |
|                     }
 | |
|                     // Store the description, if required
 | |
|                     if (descriptionCount > 0)
 | |
|                     {
 | |
|                         // Add the cm:description additional properties
 | |
|                         boolean wasMLAware = MLPropertyInterceptor.setMLAware(true);
 | |
|                         try
 | |
|                         {
 | |
|                             MLText descriptions = new MLText();
 | |
|                             String[] languages = Locale.getISOLanguages();
 | |
|                             String defaultLanguage = Locale.getDefault().getLanguage();
 | |
|                             // Create cm:description translations
 | |
|                             for (int descriptionNum = -1; descriptionNum < (descriptionCount-1); descriptionNum++)
 | |
|                             {
 | |
|                                 String language = null;
 | |
|                                 // Use the default language for the first description
 | |
|                                 if (descriptionNum == -1)
 | |
|                                 {
 | |
|                                     language = defaultLanguage;
 | |
|                                 }
 | |
|                                 else if (languages[descriptionNum].equals(defaultLanguage))
 | |
|                                 {
 | |
|                                     // Skip the default language, if we hit it
 | |
|                                     continue;
 | |
|                                 }
 | |
|                                 else
 | |
|                                 {
 | |
|                                     language = languages[descriptionNum];
 | |
|                                 }
 | |
|                                 Locale languageLocale = new Locale(language);
 | |
|                                 // For the cm:description, create new reader with a seed that changes each time
 | |
|                                 String descriptionUrl = SpoofedTextContentReader.createContentUrl(locale, seed + descriptionNum, descriptionSize);
 | |
|                                 SpoofedTextContentReader readerDescription = new SpoofedTextContentReader(descriptionUrl);
 | |
|                                 String description = readerDescription.getContentString();
 | |
|                                 descriptions.put(languageLocale, description);
 | |
|                             }
 | |
|                             nodeService.setProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION, descriptions);
 | |
|                         }
 | |
|                         finally
 | |
|                         {
 | |
|                             MLPropertyInterceptor.setMLAware(wasMLAware);
 | |
|                         }
 | |
|                     }
 | |
|                     // Success
 | |
|                     count.incrementAndGet();
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         };
 | |
|         // Batches
 | |
|         RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
 | |
|         int txnCount = (int) Math.ceil((double)fileCount / (double)filesPerTxn);
 | |
|         for (int i = 0; i < txnCount; i++)
 | |
|         {
 | |
|             txnHelper.doInTransaction(createFilesWork, false, true);
 | |
|         }
 | |
|         // Done
 | |
|         return count.get();
 | |
|     }
 | |
| }
 |