Java tutorial
/* * #%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(); } }