Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)

99377: BENCH-369: BM-0004: API and internals for Alfresco server
    - Move commons Math3 to 'core' project for general reuse
    - Clone NormalDistributionHelper class from Benchmark projects to Alfresco 'core'
    - API added: http://localhost:8080/alfresco/s/api/model/filefolder/load
      JSON:
      {
      "folderPath":"/Sites/t2/documentLibrary",
      "fileCount":"1",
      "minFileSize":"1024",
      "maxFileSize":"2048",
      "maxUniqueDocuments":"10000"
      }
    - Above JSON will create 1 file in the 't2' site document library with spoofed plain text
    - Change away from deprecated API for TransactionListenerAdapter
    - Fix imports and neatness
    - Improve FileNotFoundException details
    - Disable timestamp propagation on the parent folder to reduce CPU overhead
    - Document changes relating to the addition of cm:description properties
    - Add options to control generation of MLText cm:description fields
      - descriptionCount: number of cm:description translations to include
      - descriptionSize:  size in bytes of each cm:description translation
    - Use released 'alfresco-text-gen' V1.1
    - Use fixed text-gen component to prevent ArrayIndexOutOfBOunds
    - Tighten up error message when errors occur on reading content strings
    - Fix random seed generation bug


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@99503 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2015-03-16 13:36:33 +00:00
parent 9e3ae9c8e4
commit ebeea13b8b
14 changed files with 1441 additions and 688 deletions

View File

@@ -153,7 +153,12 @@
<constructor-arg ref="repositoryState" />
<constructor-arg ref="transactionService" />
<constructor-arg ref="repositoryHelper" />
<constructor-arg ref="FileFolderService" />
<constructor-arg ref="nodeService" />
<constructor-arg ref="contentService" />
<constructor-arg ref="policyBehaviourFilter" />
</bean>
<alias name="fileFolderLoader" alias="FileFolderLoader"/>
<!-- Multilingual specific service -->
<bean name="multilingualContentService" class="org.alfresco.repo.model.ml.MultilingualContentServiceImpl" >

View File

@@ -28,7 +28,7 @@
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-text-gen</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.alfresco.services</groupId>
@@ -85,11 +85,6 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
@@ -1001,6 +996,7 @@
<includes>
<include>**/org/alfresco/AllUnitTestsSuite.java</include>
<include>**/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java</include>
<include>**/org/alfresco/repo/model/filefolder/FileFolderLoaderTest.java</include>
</includes>
</configuration>
</execution>

View File

@@ -40,9 +40,9 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.node.integrity.IntegrityException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.util.TraceableThreadFactory;
import org.alfresco.util.transaction.TransactionListenerAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEventPublisher;

View File

@@ -510,7 +510,7 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl
// done
return content;
}
catch (IOException e)
catch (Exception e)
{
throw new ContentIOException("Failed to copy content to string: \n" +
" accessor: " + this,

View File

@@ -63,7 +63,6 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionAwareSingleton;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.TransactionalDao;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
@@ -93,6 +92,7 @@ import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.ReadWriteLockExecuter;
import org.alfresco.util.ValueProtectingMap;
import org.alfresco.util.transaction.TransactionListenerAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.ConcurrencyFailureException;

View File

@@ -18,10 +18,35 @@
*/
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.
@@ -32,53 +57,273 @@ import org.alfresco.service.transaction.TransactionService;
* <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)
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
* @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 uniqueContentCount the total number of unique files that can be generated i.e. each file will be
* one of a total number of unique files.
* @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 have a real file
* 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(
String folderPath,
int fileCount,
long minFileSize, long maxFileSize,
boolean forceBinaryStorage,
long uniqueContentCount) throws FileNotFoundException
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.");
}
return 0;
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);
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);
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();
}
}

View File

@@ -33,7 +33,6 @@ import java.util.ResourceBundle.Control;
import java.util.Set;
import java.util.Stack;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryResults;
@@ -286,6 +285,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return fileInfo;
}
@Override
public List<FileInfo> toFileInfoList(List<NodeRef> nodeRefs)
{
List<FileInfo> fileInfos = new LinkedList<FileInfo>();
@@ -319,37 +319,13 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
}
/**
* 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);
}
}
@Override
public boolean exists(NodeRef nodeRef)
{
return nodeService.exists(nodeRef);
}
@Override
public FileFolderServiceType getType(QName typeQName)
{
if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_FOLDER))
@@ -373,6 +349,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
}
@Override
public List<FileInfo> list(NodeRef contextNodeRef)
{
// execute the query
@@ -481,6 +458,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
@Override
public PagingResults<FileInfo> list(NodeRef rootNodeRef, Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps, PagingRequest pagingRequest)
{
CannedQueryResults<NodeRef> results = listImpl(rootNodeRef, null, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest);
@@ -500,16 +478,6 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
// note: similar to getChildAssocs(contextNodeRef, searchTypeQNames) but enables paging features, including max items, sorting etc (with permissions per-applied)
/**
*
* @param contextNodeRef
* @param pattern
* @param searchTypeQNames
* @param ignoreAspectQNames
* @param sortProps
* @param pagingRequest
* @return
*/
private CannedQueryResults<NodeRef> listImpl(NodeRef contextNodeRef, String pattern, Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps, PagingRequest pagingRequest)
{
Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null);
@@ -537,6 +505,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return results;
}
@Override
public List<FileInfo> listFiles(NodeRef contextNodeRef)
{
// execute the query
@@ -551,6 +520,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return results;
}
@Override
public List<FileInfo> listFolders(NodeRef contextNodeRef)
{
// execute the query
@@ -565,8 +535,8 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return results;
}
public List<FileInfo> listDeepFolders(NodeRef contextNodeRef,
SubFolderFilter filter)
@Override
public List<FileInfo> listDeepFolders(NodeRef contextNodeRef, SubFolderFilter filter)
{
List<NodeRef> nodeRefs = listSimpleDeep(contextNodeRef, false, true, filter);
@@ -614,6 +584,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return resultNodeRef;
}
@Override
public NodeRef searchSimple(NodeRef contextNodeRef, String name)
{
ParameterCheck.mandatory("name", name);
@@ -634,6 +605,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
/**
* @see #search(NodeRef, String, boolean, boolean, boolean)
*/
@Override
public List<FileInfo> search(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders)
{
return search(contextNodeRef, namePattern, true, true, includeSubFolders);
@@ -644,6 +616,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
/**
* Full search with all options
*/
@Override
public List<FileInfo> search(
NodeRef contextNodeRef,
String namePattern,
@@ -971,6 +944,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
/**
* @see #move(NodeRef, NodeRef, String)
*/
@Override
public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException
{
return moveOrCopy(sourceNodeRef, null, null, newName, true);
@@ -1006,6 +980,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
/**
* @see #moveOrCopy(NodeRef, NodeRef, String, boolean)
*/
@Override
public FileInfo copy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException
{
return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, false);
@@ -1238,11 +1213,13 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return systemPaths.contains(prefixedPath);
}
@Override
public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException
{
return createImpl(parentNodeRef, name, typeQName, null);
}
@Override
public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException
{
return createImpl(parentNodeRef, name, typeQName, assocQName);
@@ -1303,6 +1280,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return fileInfo;
}
@Override
public void delete(NodeRef nodeRef)
{
nodeService.deleteNode(nodeRef);
@@ -1364,6 +1342,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
* including the destination file or folder
* @throws FileNotFoundException if the node could not be found
*/
@Override
public List<FileInfo> getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException
{
// check the root
@@ -1450,6 +1429,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
* including the destination file or folder
* @throws FileNotFoundException if the node could not be found
*/
@Override
public List<String> getNameOnlyPath(NodeRef rootNodeRef, final NodeRef nodeRef) throws FileNotFoundException
{
// check the root
@@ -1522,11 +1502,13 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
}
@Override
public FileInfo resolveNamePath(NodeRef rootNodeRef, List<String> pathElements) throws FileNotFoundException
{
return resolveNamePath(rootNodeRef, pathElements, true);
}
@Override
public FileInfo resolveNamePath(NodeRef rootNodeRef, List<String> pathElements, boolean mustExist) throws FileNotFoundException
{
if (pathElements.size() == 0)
@@ -1540,12 +1522,13 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
for (int i = 0; i < folderCount; i++)
{
String pathElement = pathElements.get(i);
currentPath.append("/").append(pathElement);
NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement);
if (folderNodeRef == null)
{
if (mustExist)
{
throw new FileNotFoundException("Folder not found: " + currentPath);
throw new FileNotFoundException("Folder not found: " + currentPath + " (in " + rootNodeRef + ")");
}
else
{
@@ -1556,12 +1539,13 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
// we have resolved the folder path - resolve the last component
String pathElement = pathElements.get(pathElements.size() - 1);
currentPath.append("/").append(pathElement);
NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement);
if (fileNodeRef == null)
{
if (mustExist)
{
throw new FileNotFoundException("File not found: " + currentPath);
throw new FileNotFoundException("File not found: " + currentPath + " (in " + rootNodeRef + ")");
}
else
{
@@ -1580,6 +1564,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return result;
}
@Override
public FileInfo getFileInfo(NodeRef nodeRef)
{
try
@@ -1592,6 +1577,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
}
@Override
public ContentReader getReader(NodeRef nodeRef)
{
FileInfo fileInfo = toFileInfo(nodeRef, false);
@@ -1602,6 +1588,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
return contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
}
@Override
public ContentWriter getWriter(NodeRef nodeRef)
{
FileInfo fileInfo = toFileInfo(nodeRef, false);

View File

@@ -52,7 +52,6 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
@@ -86,6 +85,7 @@ import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyMap;
import org.alfresco.util.transaction.TransactionListenerAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;

View File

@@ -19,6 +19,7 @@
package org.alfresco.repo.model;
import org.alfresco.repo.model.filefolder.FileFolderDuplicateChildTest;
import org.alfresco.repo.model.filefolder.FileFolderLoaderTest;
import org.alfresco.repo.model.filefolder.FileFolderServiceImplTest;
import org.alfresco.repo.model.filefolder.FileFolderServicePropagationTest;
import org.alfresco.repo.model.filefolder.HiddenAspectTest;
@@ -52,7 +53,7 @@ import org.junit.runners.Suite.SuiteClasses;
// These need to come afterwards, as they insert extra
// interceptors which would otherwise confuse things
FileFolderServiceImplTest.class,
// TODO
FileFolderLoaderTest.class,
FileFolderDuplicateChildTest.class,
FileFolderServicePropagationTest.class
})

View File

@@ -0,0 +1,515 @@
/*
* Copyright (C) 2005-2015 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.util.List;
import java.util.Locale;
import junit.framework.TestCase;
import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.node.MLPropertyInterceptor;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
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.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
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.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import static org.junit.Assert.assertNotEquals;
/**
* @see org.alfresco.repo.model.filefolder.FileFolderLoader
* @author Derek Hulley
* @since 5.1
*/
@Category(OwnJVMTestsCategory.class)
public class FileFolderLoaderTest extends TestCase
{
private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private FileFolderLoader fileFolderLoader;
private FileFolderService fileFolderService;
private PermissionService permissionService;
private TransactionService transactionService;
private NodeService nodeService;
private String sharedHomePath;
private NodeRef hiddenFolderNodeRef;
private String hiddenFolderPath;
private NodeRef readOnlyFolderNodeRef;
private String readOnlyFolderPath;
private NodeRef writeFolderNodeRef;
private String writeFolderPath;
@Override
public void setUp() throws Exception
{
AuthenticationUtil.pushAuthentication();
RunAsWork<Void> setUpWork = new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
fileFolderLoader = (FileFolderLoader) ctx.getBean("FileFolderLoader");
fileFolderService = (FileFolderService) ctx.getBean("FileFolderService");
permissionService = (PermissionService) ctx.getBean("PermissionService");
transactionService = (TransactionService) ctx.getBean("TransactionService");
nodeService = (NodeService) ctx.getBean("nodeService");
NodeRef companyHomeNodeRef = fileFolderLoader.getRepository().getCompanyHome();
NodeRef sharedHomeNodeRef = fileFolderLoader.getRepository().getSharedHome();
List<FileInfo> sharedHomeFileInfos = fileFolderService.getNamePath(companyHomeNodeRef, sharedHomeNodeRef);
sharedHomePath = "/" + sharedHomeFileInfos.get(0).getName();
// Create a folder that will be invisible to all normal users
FileInfo hiddenFolderInfo = fileFolderService.create(sharedHomeNodeRef, "HideThis", ContentModel.TYPE_FOLDER);
hiddenFolderNodeRef = hiddenFolderInfo.getNodeRef();
hiddenFolderPath = sharedHomePath + "/HideThis";
permissionService.setInheritParentPermissions(hiddenFolderNodeRef, false);
// Create a folder that will be read-only
FileInfo readOnlyFolderInfo = fileFolderService.create(sharedHomeNodeRef, "ReadOnlyThis", ContentModel.TYPE_FOLDER);
readOnlyFolderNodeRef = readOnlyFolderInfo.getNodeRef();
readOnlyFolderPath = sharedHomePath + "/ReadOnlyThis";
permissionService.setInheritParentPermissions(readOnlyFolderNodeRef, false);
permissionService.setPermission(readOnlyFolderNodeRef, PermissionService.ALL_AUTHORITIES, PermissionService.READ, true);
// Create a folder to write to
FileInfo writeFolderInfo = fileFolderService.create(sharedHomeNodeRef, "WriteThis", ContentModel.TYPE_FOLDER);
writeFolderNodeRef = writeFolderInfo.getNodeRef();
writeFolderPath = sharedHomePath + "/WriteThis";
// Done
return null;
}
};
AuthenticationUtil.runAsSystem(setUpWork);
}
@Override
public void tearDown() throws Exception
{
RunAsWork<Void> setUpWork = new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
fileFolderService.delete(hiddenFolderNodeRef);
fileFolderService.delete(readOnlyFolderNodeRef);
fileFolderService.delete(writeFolderNodeRef);
// Done
return null;
}
};
AuthenticationUtil.runAsSystem(setUpWork);
AuthenticationUtil.popAuthentication();
}
@Test
public void testBasic()
{
assertNotNull(fileFolderLoader);
assertNotNull(sharedHomePath);
}
@Test
public void testIllegalArgs_MinMax() throws Exception
{
try
{
fileFolderLoader.createFiles(
sharedHomePath,
1, 256, 100L, 10L, Long.MAX_VALUE, false,
10, 256);
fail("Should detect min/max size issue.");
}
catch (IllegalArgumentException e)
{
// Expected
}
}
@Test
public void testIllegalArgs_DescriptionCount() throws Exception
{
try
{
fileFolderLoader.createFiles(
sharedHomePath,
1, 256, 1024L, 10L, Long.MAX_VALUE, false,
Integer.MAX_VALUE, 256);
fail("Should detect description count issue.");
}
catch (IllegalArgumentException e)
{
// Expected
}
}
@Test
public void testIllegalArgs_DescriptionSize() throws Exception
{
try
{
fileFolderLoader.createFiles(
sharedHomePath,
1, 256, 1024L, 10L, Long.MAX_VALUE, false,
10, Long.MAX_VALUE);
fail("Should detect description size issue.");
}
catch (IllegalArgumentException e)
{
// Expected
}
}
@Test
public void testNoPermissionsAtAll() throws Exception
{
try
{
fileFolderLoader.createFiles(
sharedHomePath,
0, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
fail("No permissions to see folder.");
}
catch (AuthenticationCredentialsNotFoundException e)
{
// Expected
}
}
@Test
public void testNoPermissionsToFindFolder() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser("BOB-1");
fileFolderLoader.createFiles(
hiddenFolderPath,
0, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
fail("No permissions to see folder.");
}
catch (AccessDeniedException e)
{
// Expected
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
@Test
public void testFolderMissing() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
fileFolderLoader.createFiles(
sharedHomePath + "/Missing",
0, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
fail("Folder does not exist");
}
catch (AlfrescoRuntimeException e)
{
// Expected
assertTrue(e.getCause() instanceof FileNotFoundException);
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
@Test
public void testNoPermissionsToWriteToFolder() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser("BOB-1");
fileFolderLoader.createFiles(
readOnlyFolderPath,
1, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
fail("Folder is read only. Should not be able to write to it.");
}
catch (AccessDeniedException e)
{
// Expected
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* Zero files
*/
@Test
public void testLoad_ZeroFiles() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
int created = fileFolderLoader.createFiles(
writeFolderPath,
0, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
assertEquals("Incorrect number of files generated.", 0, created);
// Count
assertEquals(0, nodeService.countChildAssocs(writeFolderNodeRef, true));
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* One file
*/
@Test
public void testLoad_OneFile() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
int created = fileFolderLoader.createFiles(
writeFolderPath,
1, 256, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
assertEquals("Incorrect number of files generated.", 1, created);
// Check the descriptions
RetryingTransactionCallback<Void> checkCallback = new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
MLPropertyInterceptor.setMLAware(true);
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(writeFolderNodeRef);
// Count
assertEquals(1, childAssocs.size());
NodeRef fileNodeRef = childAssocs.get(0).getChildRef();
MLText descriptions = (MLText) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION);
assertNotNull("No descriptions added", descriptions);
assertEquals("Incorrect number of unique descriptions added: ", 10, descriptions.size());
assertTrue("Expect the default language to be present. ",
descriptions.containsKey(new Locale(Locale.getDefault().getLanguage())));
return null;
}
};
transactionService.getRetryingTransactionHelper().doInTransaction(checkCallback, true);
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* 100 files; 10 per txn
*/
@Test
public void testLoad_02() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
int created = fileFolderLoader.createFiles(
writeFolderPath,
100, 10, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
assertEquals("Incorrect number of files generated.", 100, created);
// Count
assertEquals(100, nodeService.countChildAssocs(writeFolderNodeRef, true));
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* 15 files; 10 per txn; spoofed; different
*/
@Test
public void testLoad_03() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
int created = fileFolderLoader.createFiles(
writeFolderPath,
15, 10, 1024L, 1024L, Long.MAX_VALUE, false,
10, 256L);
assertEquals("Incorrect number of files generated.", 15, created);
// Count
assertEquals(15, nodeService.countChildAssocs(writeFolderNodeRef, true));
// Check the files
List<FileInfo> fileInfos = fileFolderService.listFiles(writeFolderNodeRef);
String lastText = null;
String lastDescr = null;
String lastUrl = null;
for (FileInfo fileInfo : fileInfos)
{
NodeRef fileNodeRef = fileInfo.getNodeRef();
// The URLs must all be unique as we wrote the physical binaries
ContentReader reader = fileFolderService.getReader(fileNodeRef);
assertEquals("UTF-8", reader.getEncoding());
assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, reader.getMimetype());
assertEquals(1024L, reader.getSize());
if (lastUrl == null)
{
lastUrl = reader.getContentUrl();
}
else
{
assertNotEquals("We expect different URLs: ", lastUrl, reader.getContentUrl());
lastUrl = reader.getContentUrl();
}
// Check content
if (lastText == null)
{
lastText = reader.getContentString();
}
else
{
String currentStr = reader.getContentString();
assertNotEquals("All text must differ due to varying seed. ", lastText, currentStr);
lastText = currentStr;
}
// Check description
if (lastDescr == null)
{
lastDescr = (String) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION);
assertEquals("cm:description length is incorrect. ", 256, lastDescr.getBytes().length);
}
else
{
String currentDescr = (String) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION);
assertNotEquals("All descriptions must differ due to varying seed. ", lastDescr, currentDescr);
lastDescr = currentDescr;
}
}
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
/**
* 10 files; 10 per txn; force storage; identical
*/
@Test
public void testLoad_04() throws Exception
{
try
{
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
int created = fileFolderLoader.createFiles(
writeFolderPath,
10, 10, 1024L, 1024L, 1L, true,
10, 256L);
assertEquals("Incorrect number of files generated.", 10, created);
// Count
assertEquals(10, nodeService.countChildAssocs(writeFolderNodeRef, true));
// Check the files
List<FileInfo> fileInfos = fileFolderService.listFiles(writeFolderNodeRef);
String lastText = null;
String lastDescr = null;
String lastUrl = null;
for (FileInfo fileInfo : fileInfos)
{
NodeRef fileNodeRef = fileInfo.getNodeRef();
// The URLs must all be unique as we wrote the physical binaries
ContentReader reader = fileFolderService.getReader(fileNodeRef);
assertEquals("UTF-8", reader.getEncoding());
assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, reader.getMimetype());
assertEquals(1024L, reader.getSize());
if (lastUrl == null)
{
lastUrl = reader.getContentUrl();
}
else
{
assertNotEquals("We expect unique URLs: ", lastUrl, reader.getContentUrl());
lastUrl = reader.getContentUrl();
}
// Check content
if (lastText == null)
{
lastText = reader.getContentString();
}
else
{
String currentStr = reader.getContentString();
assertEquals("All text must be identical due to same seed. ", lastText, currentStr);
lastText = currentStr;
}
// Check description
if (lastDescr == null)
{
lastDescr = (String) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION);
assertEquals("cm:description length is incorrect. ", 256, lastDescr.getBytes().length);
}
else
{
String currentDescr = (String) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION);
assertEquals("All descriptions must be identical due to varying seed. ", lastDescr, currentDescr);
lastDescr = currentDescr;
}
}
}
finally
{
AuthenticationUtil.popAuthentication();
}
}
}

View File

@@ -52,6 +52,7 @@ import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.alfresco.util.transaction.TransactionListenerAdapter;
import org.apache.commons.lang.mutable.MutableInt;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -725,8 +726,11 @@ public class RetryingTransactionHelperTest extends TestCase
assertFalse("New transaction has not started", txnId.equals(listener.newTxnId));
}
private void runThreads(final RetryingTransactionHelper txnHelper, final List<Throwable> caughtExceptions,
Pair<Integer, Integer>... startDurationPairs)
@SuppressWarnings("unchecked")
private void runThreads(
final RetryingTransactionHelper txnHelper,
final List<Throwable> caughtExceptions,
final Pair<Integer, Integer>... startDurationPairs)
{
ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10));