diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index 4bd9c0c32e..8f798a11e8 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -147,6 +147,13 @@ + + + + + + + diff --git a/pom.xml b/pom.xml index f1a63b8ec3..6d7edeea5c 100644 --- a/pom.xml +++ b/pom.xml @@ -25,12 +25,17 @@ alfresco-mbeans ${project.version} - - org.alfresco.services - alfresco-events - 1.2.4 - - + + org.alfresco + alfresco-text-gen + 1.0-SNAPSHOT + + + org.alfresco.services + alfresco-events + 1.2.4 + + com.sun.mail javax.mail 1.5.2 @@ -995,6 +1000,7 @@ **/org/alfresco/AllUnitTestsSuite.java + **/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java diff --git a/source/java/org/alfresco/repo/admin/RepositoryState.java b/source/java/org/alfresco/repo/admin/RepositoryState.java index 6c65ad5c9f..7eec55f22a 100644 --- a/source/java/org/alfresco/repo/admin/RepositoryState.java +++ b/source/java/org/alfresco/repo/admin/RepositoryState.java @@ -21,6 +21,8 @@ package org.alfresco.repo.admin; import java.util.concurrent.locks.ReentrantReadWriteLock; /** + * A class that maintains a thread-safe ready indicator on the current bootstrap state of the repository. + * * @author Andy * */ @@ -29,6 +31,12 @@ public class RepositoryState private boolean bootstrapping; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + /** + * Determine if the repository is ready to use. + * + * @return true if the repository bootstrap process is still going, + * or false if the repository is ready to use + */ public boolean isBootstrapping() { this.lock.readLock().lock(); diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentReader.java b/source/java/org/alfresco/repo/content/filestore/FileContentReader.java index 1b5f04f354..a48c09c389 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentReader.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentReader.java @@ -239,7 +239,9 @@ public class FileContentReader extends AbstractContentReader /** * @return Returns false as this is a reader + * @deprecated Since 5.1. This method has no value: a file reader can never write (DH: 2015/02/17) */ + @Deprecated public boolean canWrite() { return false; // we only allow reading diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java index 0f82245879..b55dd370b8 100644 --- a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java +++ b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java @@ -55,7 +55,12 @@ import org.springframework.context.event.ContextRefreshedEvent; * are generated using information from the {@link ContentContext simple content context}. *

* The file names obey, as they must, the URL naming convention - * as specified in the {@link org.alfresco.repo.content.ContentStore ContentStore interface}. + * as specified in the {@link org.alfresco.repo.content.ContentStore ContentStore interface}.
+ * The protocols handled are: + *

    + *
  • {@link #STORE_PROTOCOL store}: These URLs can be generated by this implementation and are file references within the root directory.
  • + *
  • {@link #SPOOF_PROTOCOL spoof}: These URLs are never generated by the implementation but represent spoofed binary text stream data: TODO
  • + *
* * @author Derek Hulley */ @@ -64,10 +69,11 @@ public class FileContentStore implements ApplicationContextAware, ApplicationListener { /** - * store is the new prefix for file content URLs + * store is the default prefix for file content URLs * @see ContentStore#PROTOCOL_DELIMITER */ public static final String STORE_PROTOCOL = "store"; + public static final String SPOOF_PROTOCOL = "spoof"; private static final Log logger = LogFactory.getLog(FileContentStore.class); @@ -343,10 +349,20 @@ public class FileContentStore Pair urlParts = super.getContentUrlParts(contentUrl); String protocol = urlParts.getFirst(); String relativePath = urlParts.getSecond(); + return makeFile(protocol, relativePath); + } + + /** + * Make the file based on the content URL parts. + * + * @param protocol must be {@link ContentStore#PROTOCOL_DELIMITER} for this class + */ + private File makeFile(String protocol, String relativePath) + { // Check the protocol if (!protocol.equals(FileContentStore.STORE_PROTOCOL)) { - throw new UnsupportedContentUrlException(this, contentUrl); + throw new UnsupportedContentUrlException(this, protocol + PROTOCOL_DELIMITER + relativePath); } // get the file File file = new File(rootDirectory, relativePath); @@ -367,12 +383,23 @@ public class FileContentStore /** * Performs a direct check against the file for its existence. + * For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists. */ @Override public boolean exists(String contentUrl) { - File file = makeFile(contentUrl); - return file.exists(); + Pair urlParts = super.getContentUrlParts(contentUrl); + String protocol = urlParts.getFirst(); + String relativePath = urlParts.getSecond(); + if (protocol.equals(SPOOF_PROTOCOL)) + { + return true; + } + else + { + File file = makeFile(protocol, relativePath); + return file.exists(); + } } /** @@ -420,9 +447,18 @@ public class FileContentStore */ public ContentReader getReader(String contentUrl) { + Pair urlParts = super.getContentUrlParts(contentUrl); + String protocol = urlParts.getFirst(); + String relativePath = urlParts.getSecond(); + // Handle the spoofed URL + if (protocol.equals(SPOOF_PROTOCOL)) + { + return new SpoofedTextContentReader(contentUrl); + } + // else, it's a real file we are after try { - File file = makeFile(contentUrl); + File file = makeFile(protocol, relativePath); ContentReader reader = null; if (file.exists()) { @@ -590,11 +626,21 @@ public class FileContentStore { throw new UnsupportedOperationException("This store is currently read-only: " + this); } - // ignore files that don't exist - File file = makeFile(contentUrl); + // Dig out protocol + Pair urlParts = super.getContentUrlParts(contentUrl); + String protocol = urlParts.getFirst(); + String relativePath = urlParts.getSecond(); + if (protocol.equals(SPOOF_PROTOCOL)) + { + // This is not a failure but the content can never actually be deleted + return false; + } + // Handle regular files based on the real files + File file = makeFile(protocol, relativePath); boolean deleted = false; if (!file.exists()) { + // File does not exist deleted = true; } else @@ -618,8 +664,6 @@ public class FileContentStore return deleted; } - - /** * Creates a new content URL. This must be supported by all * stores that are compatible with Alfresco. diff --git a/source/java/org/alfresco/repo/content/filestore/SpoofedTextContentReader.java b/source/java/org/alfresco/repo/content/filestore/SpoofedTextContentReader.java new file mode 100644 index 0000000000..d1fef8929d --- /dev/null +++ b/source/java/org/alfresco/repo/content/filestore/SpoofedTextContentReader.java @@ -0,0 +1,311 @@ +/* + * 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 . + */ +package org.alfresco.repo.content.filestore; + +import java.io.File; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.repo.content.AbstractContentReader; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.textgen.TextGenerator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +/** + * Provides access to text data that is generated when requested. + *

+ * The URL has the format: spoofed://{locale=en_GB,seed=12345,length=1024,strings=["Alfresco", "Cloud"]} + *

+ * The lexicon for the given locale is found by taking the language part of the locale (en in en_GB) + * and finding the resource alfresco/textgen/lexicon-stem-en.txt. + * + * @see TextGenerator + * + * @author Derek Hulley + * @since 5.1 + */ +public class SpoofedTextContentReader extends AbstractContentReader +{ + public static final String LEXICON_STEM_PATH = "alfresco/textgen/lexicon-stem-@@LOCALE@@.txt"; + public static final String KEY_LOCALE = "locale"; + public static final String KEY_SEED = "seed"; + public static final String KEY_SIZE = "size"; + public static final String KEY_WORDS = "words"; + + private static Map textGeneratorsByLocale = new HashMap(); + private static ReentrantReadWriteLock textGeneratorsLock = new ReentrantReadWriteLock(); + + private static final Log logger = LogFactory.getLog(SpoofedTextContentReader.class); + + private final TextGenerator textGenerator; + private final long seed; + private final long size; + private final String[] words; + + /** + * Get a text generator for the given locale + * + * @throws RuntimeException if the locale has no lexicon exists for the locale + */ + public static TextGenerator getTextGenerator(Locale locale) + { + textGeneratorsLock.readLock().lock(); + try + { + TextGenerator tg = textGeneratorsByLocale.get(locale); + if (tg != null) + { + return tg; + } + } + finally + { + textGeneratorsLock.readLock().unlock(); + } + // Create one + textGeneratorsLock.writeLock().lock(); + try + { + // Double check + TextGenerator tg = textGeneratorsByLocale.get(locale); + if (tg != null) + { + return tg; + } + // Create it + String lang = locale.getLanguage(); + String configPath = LEXICON_STEM_PATH.replace("@@LOCALE@@", lang); + tg = new TextGenerator(configPath); + // Store it + textGeneratorsByLocale.put(locale, tg); + // Done + return tg; + } + finally + { + textGeneratorsLock.writeLock().unlock(); + } + } + + /** + * Helper to create a content URL that represents spoofed text + * + * @param locale the text local (must be supported by an appropriate lexicon config resource) + * @param seed numerical seed to ensure repeatable sequences of random text + * @param size the size (bytes) of the text to generate + * @param words additional words with decreasing frequency + * @return the content URL + * + * @throws IllegalArgumentException if the resulting URL exceeds 255 characters + */ + @SuppressWarnings("unchecked") + public static String createContentUrl(Locale locale, long seed, long size, String ... words) + { + if (locale == null || size < 0L) + { + throw new IllegalArgumentException("Locale must be supplied and size must be zero or greater."); + } + + // Make sure that there is a text generator available + SpoofedTextContentReader.getTextGenerator(locale); + + // Build map + String url = null; + try + { + JSONObject jsonObj = new JSONObject(); + jsonObj.put(KEY_LOCALE, locale.toString()); + jsonObj.put(KEY_SEED, Long.valueOf(seed).toString()); + jsonObj.put(KEY_SIZE, Long.valueOf(size).toString()); + JSONArray jsonWords = new JSONArray(); + for (String word : words) + { + if (word == null) + { + throw new IllegalArgumentException("Words to inject into the document may not be null."); + } + jsonWords.add(word); + } + jsonObj.put(KEY_WORDS, jsonWords); + + url = FileContentStore.SPOOF_PROTOCOL + "://" + jsonObj.toString(); + if (url.length() > 255) + { + throw new IllegalArgumentException("Content URLs can be up to 255 characters. Have " + url.length() + " characters: " + url); + } + return url; + } + catch (IllegalArgumentException e) + { + // Let these out as they are + throw e; + } + catch (Exception e) + { + throw new RuntimeException("Unable to create content URL using " + locale + ", " + seed + ", " + size + ", " + words, e); + } + } + + /** + * @param url a URL describing the type of text to produce (see class comments) + */ + public SpoofedTextContentReader(String url) + { + super(url); + if (url.length() > 255) + { + throw new IllegalArgumentException("A content URL is limited to 255 characters: " + url); + } + // Split out data part + int index = url.indexOf(ContentStore.PROTOCOL_DELIMITER); + if (index <= 0 || !url.startsWith(FileContentStore.SPOOF_PROTOCOL)) + { + throw new RuntimeException("URL not supported by this reader: " + url); + } + String urlData = url.substring(index + 3, url.length()); + // Parse URL + try + { + JSONParser parser = new JSONParser(); + JSONObject mappedData = (JSONObject) parser.parse(urlData); + + String jsonLocale = mappedData.containsKey(KEY_LOCALE) ? (String) mappedData.get(KEY_LOCALE) : Locale.ENGLISH.toString(); + String jsonSeed = mappedData.containsKey(KEY_SEED) ? (String) mappedData.get(KEY_SEED) : "0"; + String jsonSize = mappedData.containsKey(KEY_SIZE) ? (String) mappedData.get(KEY_SIZE) : "1024"; + JSONArray jsonWords = mappedData.containsKey(KEY_WORDS) ? (JSONArray) mappedData.get(KEY_WORDS) : new JSONArray(); + // Get the text generator + Locale locale = new Locale(jsonLocale); + seed = Long.valueOf(jsonSeed); + size = Long.valueOf(jsonSize); + words = new String[jsonWords.size()]; + for (int i = 0; i < words.length; i++) + { + words[i] = (String) jsonWords.get(i); + } + this.textGenerator = SpoofedTextContentReader.getTextGenerator(locale); + // Set the base class storage for external information + super.setLocale(locale); + super.setEncoding("UTF-8"); + super.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + } + catch (Exception e) + { + throw new RuntimeException("Unable to interpret URL: " + url, e); + } + + } + + /** + * @return true always + */ + public boolean exists() + { + return true; + } + + /** + * @return the text generator that will make the spoofed text + */ + public TextGenerator getTextGenerator() + { + return textGenerator; + } + + /** + * @return the random seed for the spoofed text + */ + public long getSeed() + { + return seed; + } + + /** + * @return the words to add to the spoofed text + */ + public String[] getWords() + { + return words; + } + + /** + * @return spoofed text size + */ + public long getSize() + { + return size; + } + + /** + * @see File#lastModified() + */ + public long getLastModified() + { + return 0L; + } + + /** + * The URL of the write is known from the start and this method contract states + * that no consideration needs to be taken w.r.t. the stream state. + */ + @Override + protected ContentReader createReader() throws ContentIOException + { + SpoofedTextContentReader reader = new SpoofedTextContentReader(getContentUrl()); + return reader; + } + + @Override + protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException + { + try + { + // Interpret the URL to generate the text + InputStream textStream = textGenerator.getInputStream(seed, size, words); + ReadableByteChannel textChannel = Channels.newChannel(textStream); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Opened read channel to random text for URL: " + getContentUrl()); + } + return textChannel; + } + catch (Throwable e) + { + throw new ContentIOException("Failed to read channel: " + this, e); + } + } + + @Override + protected final void setContentUrl(String contentUrl) + { + throw new UnsupportedOperationException("The URL is static and cannot be changed."); + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderLoader.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderLoader.java new file mode 100644 index 0000000000..c01428d08d --- /dev/null +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderLoader.java @@ -0,0 +1,84 @@ +/* + * 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 . + */ +package org.alfresco.repo.model.filefolder; + +import org.alfresco.repo.admin.RepositoryState; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.transaction.TransactionService; + +/** + * Class to aid in the generation of file-folder data structures for load test purposes. + *

+ * 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. + *

+ * 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. + * + * @author Derek Hulley + * @since 5.1 + */ +public class FileFolderLoader +{ + private final RepositoryState repoState; + private final TransactionService transactionService; + private final Repository repositoryHelper; + + /** + * @param repoState keep track of repository readiness + * @param transactionService ensure proper rollback, where required + * @param repositoryHelper access standard repository paths + */ + public FileFolderLoader(RepositoryState repoState, TransactionService transactionService, Repository repositoryHelper) + { + this.repoState = repoState; + this.transactionService = transactionService; + this.repositoryHelper = repositoryHelper; + } + + /** + * + * @param folderPath the full path to the folder + * @param fileCount the number of files to create + * @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 forceBinaryStorage true to actually write the spoofed text data to the binary store + * i.e. the physical underlying storage will have a real file + * @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 + { + if (repoState.isBootstrapping()) + { + throw new IllegalStateException("Repository is still bootstrapping."); + } + return 0; + } +} diff --git a/source/test-java/org/alfresco/repo/content/AbstractReadOnlyContentStoreTest.java b/source/test-java/org/alfresco/repo/content/AbstractReadOnlyContentStoreTest.java index 9a2e858f59..35b7baaf26 100644 --- a/source/test-java/org/alfresco/repo/content/AbstractReadOnlyContentStoreTest.java +++ b/source/test-java/org/alfresco/repo/content/AbstractReadOnlyContentStoreTest.java @@ -18,11 +18,6 @@ */ package org.alfresco.repo.content; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; @@ -46,6 +41,11 @@ import org.junit.Test; import org.junit.rules.TestName; import org.springframework.context.ApplicationContext; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * Abstract base class that provides a set of tests for implementations * of {@link ContentStore}. diff --git a/source/test-java/org/alfresco/repo/content/ContentFullContextTestSuite.java b/source/test-java/org/alfresco/repo/content/ContentFullContextTestSuite.java index 35f43dd31b..d4cec1fdc9 100644 --- a/source/test-java/org/alfresco/repo/content/ContentFullContextTestSuite.java +++ b/source/test-java/org/alfresco/repo/content/ContentFullContextTestSuite.java @@ -26,6 +26,7 @@ import org.alfresco.repo.content.cleanup.ContentStoreCleanerTest; import org.alfresco.repo.content.filestore.FileContentStoreTest; import org.alfresco.repo.content.filestore.NoRandomAccessFileContentStoreTest; import org.alfresco.repo.content.filestore.ReadOnlyFileContentStoreTest; +import org.alfresco.repo.content.filestore.SpoofedTextContentReaderTest; import org.alfresco.repo.content.replication.ContentStoreReplicatorTest; import org.alfresco.repo.content.replication.ReplicatingContentStoreTest; @@ -47,6 +48,7 @@ public class ContentFullContextTestSuite extends TestSuite // These tests need a full context, at least for now suite.addTestSuite(ContentStoreCleanerTest.class); //suite.addTestSuite(CharsetFinderTest.class); + suite.addTest(new JUnit4TestAdapter(SpoofedTextContentReaderTest.class)); suite.addTest(new JUnit4TestAdapter(FileContentStoreTest.class)); suite.addTest(new JUnit4TestAdapter(NoRandomAccessFileContentStoreTest.class)); suite.addTest(new JUnit4TestAdapter(ReadOnlyFileContentStoreTest.class)); diff --git a/source/test-java/org/alfresco/repo/content/filestore/FileContentStoreTest.java b/source/test-java/org/alfresco/repo/content/filestore/FileContentStoreTest.java index d0440f9ceb..e0908ab4d2 100644 --- a/source/test-java/org/alfresco/repo/content/filestore/FileContentStoreTest.java +++ b/source/test-java/org/alfresco/repo/content/filestore/FileContentStoreTest.java @@ -18,14 +18,9 @@ */ package org.alfresco.repo.content.filestore; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.File; import java.nio.ByteBuffer; +import java.util.Locale; import org.alfresco.repo.content.AbstractWritableContentStoreTest; import org.alfresco.repo.content.ContentContext; @@ -35,12 +30,21 @@ import org.alfresco.repo.content.ContentLimitProvider.SimpleFixedLimitProvider; import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.content.ContentStore; import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.test_category.OwnJVMTestsCategory; import org.alfresco.util.TempFileProvider; +import org.junit.After; import org.junit.Before; +import org.junit.Test; import org.junit.experimental.categories.Category; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * Tests read and write functionality for the store. * @@ -64,6 +68,13 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest getName()); store.setDeleteEmptyDirs(true); + // Do not need super class's transactions + } + + @After + public void after() + { + // Do not need super class's transactions } @Override @@ -76,6 +87,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest * Checks that the store disallows concurrent writers to be issued to the same URL. */ @SuppressWarnings("unused") + @Test public void testConcurrentWriteDetection() throws Exception { ByteBuffer buffer = ByteBuffer.wrap("Something".getBytes()); @@ -98,6 +110,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest } @Override + @Test public void testRootLocation() throws Exception { ContentStore store = getStore(); @@ -111,6 +124,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest * Ensures that the size is something other than -1 or Long.MAX_VALUE */ @Override + @Test public void testSpaceFree() throws Exception { ContentStore store = getStore(); @@ -123,6 +137,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest * Ensures that the size is something other than -1 or Long.MAX_VALUE */ @Override + @Test public void testSpaceTotal() throws Exception { ContentStore store = getStore(); @@ -135,6 +150,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest /** * Empty parent directories should be removed when a URL is removed. */ + @Test public void testDeleteRemovesEmptyDirs() throws Exception { ContentStore store = getStore(); @@ -161,6 +177,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest /** * Only non-empty directories should be deleted. */ + @Test public void testDeleteLeavesNonEmptyDirs() { ContentStore store = getStore(); @@ -198,6 +215,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest /** * Empty parent directories are not deleted if the store is configured not to. */ + @Test public void testNoParentDirsDeleted() throws Exception { store.setDeleteEmptyDirs(false); @@ -221,6 +239,7 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest * the expected exception. * @since Thor */ + @Test public void testWriteFileWithSizeLimit() throws Exception { ContentWriter writer = getWriter(); @@ -247,9 +266,10 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest assertTrue("Stream close not detected", writer.isClosed()); } - /* + /** * Test for MNT-12301 case. */ + @Test public void testFileAccessOutsideStoreRoot() { String url = FileContentStore.STORE_PROTOCOL + ContentStore.PROTOCOL_DELIMITER + "../somefile.bin"; @@ -295,6 +315,31 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest } } + /** + * Ensure that the store is able to produce readers for spoofed text. + * + * @since 5.1 + */ + @Test + public void testSpoofedContent() throws Exception + { + String url = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 0L, 1024L); + ContentContext ctx = new ContentContext(null, url); + try + { + store.getWriter(ctx); + fail("FileContentStore should report that all 'spoof' content exists."); + } + catch (ContentExistsException e) + { + // Expected + } + assertFalse("Deletion should be 'false'.", store.delete(url)); + assertTrue("All spoofed content already exists!", store.exists(url)); + ContentReader reader = store.getReader(url); + assertTrue(reader instanceof SpoofedTextContentReader); + assertEquals(1024L, reader.getContentString().getBytes("UTF-8").length); + } private void assertDirExists(File root, String dir) { diff --git a/source/test-java/org/alfresco/repo/content/filestore/SpoofedTextContentReaderTest.java b/source/test-java/org/alfresco/repo/content/filestore/SpoofedTextContentReaderTest.java new file mode 100644 index 0000000000..36ea308501 --- /dev/null +++ b/source/test-java/org/alfresco/repo/content/filestore/SpoofedTextContentReaderTest.java @@ -0,0 +1,196 @@ +/* + * 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 . + */ +package org.alfresco.repo.content.filestore; + +import java.io.InputStream; +import java.util.Locale; + +import junit.framework.TestCase; + +import org.alfresco.repo.content.AbstractContentReader; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.test_category.OwnJVMTestsCategory; +import org.junit.Before; +import org.junit.experimental.categories.Category; +import org.springframework.util.FileCopyUtils; + +/** + * Text spoofing as a {@link ContentReader} + * + * @see SpoofedTextContentReader + * + * @author Derek Hulley + * @since 5.1 + */ +@Category(OwnJVMTestsCategory.class) +public class SpoofedTextContentReaderTest extends TestCase +{ + @Before + public void before() + { + // Nothing + } + + public void testStaticUrlHandlingErr() + { + try + { + SpoofedTextContentReader.createContentUrl(null, 12345L, 1024L); + fail(); + } + catch (IllegalArgumentException e) + { + // Expected + } + try + { + SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, -1L); + fail(); + } + catch (IllegalArgumentException e) + { + // Expected + } + try + { + SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, 1024L, (String) null); + fail(); + } + catch (IllegalArgumentException e) + { + // Expected + } + try + { + SpoofedTextContentReader.createContentUrl(Locale.FRENCH, 12345L, 1024L); + fail(); + } + catch (RuntimeException e) + { + // Expected + } + try + { + SpoofedTextContentReader.createContentUrl( + Locale.ENGLISH, 12345L, 1024L, + "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", + "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", + "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ", "1234567890ABCDEFGHIJ"); + fail(); + } + catch (IllegalArgumentException e) + { + // Expected + } + } + + public void testStaticUrlForm_01() + { + // To URL + String url = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, 1024L, "harry"); + assertTrue(url.startsWith("spoof://{")); + assertTrue(url.contains("\"locale\":\"en\"")); + assertTrue(url.contains("\"seed\":\"12345\"")); + assertTrue(url.contains("\"size\":\"1024\"")); + assertTrue(url.contains("\"words\":[\"harry\"]")); + assertTrue(url.endsWith("}")); + // From Reader + SpoofedTextContentReader reader = new SpoofedTextContentReader(url); + assertNotNull(reader.getTextGenerator()); + assertEquals(Locale.ENGLISH, reader.getLocale()); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, reader.getMimetype()); + assertEquals("UTF-8", reader.getEncoding()); + assertEquals(12345L, reader.getSeed()); + assertEquals(1024L, reader.getSize()); + assertNotNull(reader.getWords()); + assertEquals(1, reader.getWords().length); + assertEquals("harry", reader.getWords()[0]); + } + + public void testStaticUrlForm_02() + { + // To URL + String url = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, 1024L); + assertTrue(url.startsWith("spoof://{")); + assertTrue(url.contains("\"locale\":\"en\"")); + assertTrue(url.contains("\"seed\":\"12345\"")); + assertTrue(url.contains("\"size\":\"1024\"")); + assertTrue(url.contains("\"words\":[]")); + assertTrue(url.endsWith("}")); + // From Reader + SpoofedTextContentReader reader = new SpoofedTextContentReader(url); + assertNotNull(reader.getTextGenerator()); + assertEquals(Locale.ENGLISH, reader.getLocale()); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, reader.getMimetype()); + assertEquals("UTF-8", reader.getEncoding()); + assertEquals(12345L, reader.getSeed()); + assertEquals(1024L, reader.getSize()); + assertNotNull(reader.getWords()); + assertEquals(0, reader.getWords().length); + } + + public void testGetContentString_01() + { + // To URL + String url = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, 56L, "harry"); + // To Reader + ContentReader reader = new SpoofedTextContentReader(url); + String readerText = reader.getContentString(); + assertEquals("harry have voice the from countered growth invited ", readerText); + // Cannot repeat + try + { + reader.getContentString(); + fail("Should not be able to reread content."); + } + catch (ContentIOException e) + { + // Expected + } + // Get a new Reader + reader = reader.getReader(); + // Get exactly the same text + assertEquals(readerText, reader.getContentString()); + } + + public void testGetContentBinary_01() throws Exception + { + // To URL + String url = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 12345L, 56L, "harry"); + // To Reader + ContentReader reader = new SpoofedTextContentReader(url); + InputStream is = reader.getContentInputStream(); + try + { + byte[] bytes = FileCopyUtils.copyToByteArray(is); + assertEquals(56L, bytes.length); + } + finally + { + is.close(); + } + // Compare readers + ContentReader copyOne = reader.getReader(); + ContentReader copyTwo = reader.getReader(); + // Get exactly the same binaries + assertTrue(AbstractContentReader.compareContentReaders(copyOne, copyTwo)); + } +} diff --git a/source/test-java/org/alfresco/repo/model/ModelTestSuite.java b/source/test-java/org/alfresco/repo/model/ModelTestSuite.java index dce37427c6..26c31931f7 100644 --- a/source/test-java/org/alfresco/repo/model/ModelTestSuite.java +++ b/source/test-java/org/alfresco/repo/model/ModelTestSuite.java @@ -52,6 +52,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 FileFolderDuplicateChildTest.class, FileFolderServicePropagationTest.class }) diff --git a/source/test-java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java b/source/test-java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java index 747df43694..6f87b52fa7 100644 --- a/source/test-java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java +++ b/source/test-java/org/alfresco/repo/model/filefolder/FileFolderPerformanceTester.java @@ -24,12 +24,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import junit.framework.TestCase; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.SpoofedTextContentReader; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; @@ -41,6 +43,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -164,9 +167,10 @@ public class FileFolderPerformanceTester extends TestCase *

* Each creation (file or folder) uses the PROPAGATION REQUIRED transaction declaration. * - * @param parentNodeRef the level zero parent - * @param randomOrder true if each thread must put the children into the folders in a random order - * @return Returns the average time (ms) to create the files only + * @param parentNodeRef the level zero parent + * @param randomOrder true if each thread must put the children into the folders in a random order + * @param realFile true if a real binary must be streamed into the node + * @return Returns the average time (ms) to create the files only */ private void buildStructure( final NodeRef parentNodeRef, @@ -175,6 +179,7 @@ public class FileFolderPerformanceTester extends TestCase final int folderCount, final int batchCount, final int filesPerBatch, + final boolean realFile, final double[] dumpPoints) { RetryingTransactionCallback createFoldersCallback = new RetryingTransactionCallback() @@ -240,11 +245,25 @@ public class FileFolderPerformanceTester extends TestCase GUID.generate(), ContentModel.TYPE_CONTENT); NodeRef nodeRef = fileInfo.getNodeRef(); - // write the content - ContentWriter writer = fileFolderService.getWriter(nodeRef); - writer.setEncoding("UTF-8"); - writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - writer.putContent(dataFile); + if (realFile) + { + // write the content + ContentWriter writer = fileFolderService.getWriter(nodeRef); + writer.setEncoding("UTF-8"); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.putContent(dataFile); + } + else + { + // Spoof some content + String contentUrl = SpoofedTextContentReader.createContentUrl( + Locale.ENGLISH, + (long) Math.random() * 1000L, + (long) Math.random() * 1024L); + SpoofedTextContentReader reader = new SpoofedTextContentReader(contentUrl); + ContentData contentData = reader.getContentData(); + nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData); + } } // done return null; @@ -264,10 +283,11 @@ public class FileFolderPerformanceTester extends TestCase if (percentComplete > 0) { - logger.debug("\n" + + System.out.println("\n" + "[" + Thread.currentThread().getName() + "] \n" + " Created " + (currentBatchCount*filesPerBatch) + " files in each of " + folderCount + - " folders (" + (randomOrder ? "shuffled" : "in order") + "): \n" + + " folders (" + (randomOrder ? "shuffled" : "in order") + ")" + + " with " + (realFile ? "real files" : "spoofed content") + " :\n" + " Progress: " + String.format("%9.2f", percentComplete) + " percent complete \n" + " Average: " + String.format("%10.2f", average) + " ms per file \n" + " Average: " + String.format("%10.2f", 1000.0/average) + " files per second"); @@ -276,7 +296,7 @@ public class FileFolderPerformanceTester extends TestCase }; // kick off the required number of threads - logger.debug("\n" + + System.out.println("\n" + "Starting " + threadCount + " threads loading " + (batchCount * filesPerBatch) + " files in each of " + folderCount + @@ -463,8 +483,8 @@ public class FileFolderPerformanceTester extends TestCase readStructure(rootFolderRef, 1, 1, new double[] {0.25, 0.50, 0.75}); } */ - // Load: 800 ordered files per folder (into 3 folders) using 4 threads - public void test_4_ordered_3_2_100() throws Exception + /** Load: 800 ordered files per folder (into 3 folders) using 4 threads using spoofed text */ + public void test_4_ordered_3_2_100_spoofed() throws Exception { buildStructure( rootFolderRef, @@ -473,6 +493,7 @@ public class FileFolderPerformanceTester extends TestCase 3, 2, 100, + false, new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); System.out.println("rootFolderRef: "+rootFolderRef); @@ -480,8 +501,8 @@ public class FileFolderPerformanceTester extends TestCase readStructure(rootFolderRef, 4, 5, new double[] {0.25, 0.50, 0.75}); } - // Load: 800 shuffled files per folder (into 3 folders) using 4 threads - public void test_4_shuffled_3_2_100() throws Exception + /** Load: 800 shuffled files per folder (into 3 folders) using 4 threads using spoofed text */ + public void test_4_shuffled_3_2_100_spoofed() throws Exception { buildStructure( rootFolderRef, @@ -490,6 +511,25 @@ public class FileFolderPerformanceTester extends TestCase 3, 2, 100, + false, + new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); + + System.out.println("rootFolderRef: "+rootFolderRef); + + readStructure(rootFolderRef, 4, 5, new double[] {0.25, 0.50, 0.75}); + } + + /** Load: 800 shuffled files per folder (into 3 folders) using 4 threads using real text */ + public void test_4_shuffled_3_2_100_real() throws Exception + { + buildStructure( + rootFolderRef, + 4, + true, + 3, + 2, + 100, + true, new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); System.out.println("rootFolderRef: "+rootFolderRef); @@ -497,23 +537,6 @@ public class FileFolderPerformanceTester extends TestCase readStructure(rootFolderRef, 4, 5, new double[] {0.25, 0.50, 0.75}); } -/* - // Load: 50000 ordered files per folder (into 1 folder) using 1 thread - public void test_1_ordered_1_5000_10() throws Exception - { - buildStructure( - rootFolderRef, - 1, - false, - 1, - 5000, - 10, - new double[] {0.01, 0.02, 0.03, 0.04, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90}); - - readStructure(rootFolderRef, 1, 1, new double[] {0.25, 0.50, 0.75}); - } -*/ - /** * Create a bunch of files and folders in a folder and then run multi-threaded directory * listings against it. diff --git a/source/test-resources/log4j/log4j.properties b/source/test-resources/log4j/log4j.properties index 610d1e9e21..943cb8f036 100644 --- a/source/test-resources/log4j/log4j.properties +++ b/source/test-resources/log4j/log4j.properties @@ -1,2 +1,3 @@ ## Test to see that Log4J additions are picked up -log4j.logger.org.alfresco.repo.admin.Log4JHierarchyInitTest=DEBUG \ No newline at end of file +log4j.logger.org.alfresco.repo.admin.Log4JHierarchyInitTest=DEBUG +log4j.logger.org.alfresco.repo.model.filefolder.FileFolderPerformanceTester=DEBUG \ No newline at end of file