diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/model/filefolder/load.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/model/filefolder/load.post.desc.xml new file mode 100644 index 0000000000..9f613f950c --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/model/filefolder/load.post.desc.xml @@ -0,0 +1,25 @@ + + Load files into a folder + + The following properties may be set. + +
+
folderPath
path of the folder to use (default:---)
+
fileCount
the number of files to create (default:100)
+
filesPerTxn
the number of files committed at a time (default:100)
+
minFileSize
the smallest file size (standard deviation lower limit) (default:80K)
+
maxFileSize
the largest file size (standard deviation upper limit) (default:120K)
+
maxUniqueDocuments
total number of random unique text combinations from which to choose (default:Long.MAX_VALUE)
+
forceBinaryStorage
force the text to be written to permanent storage, otherwise it is spoofed (default:false)
+
descriptionCount
the number of cm:description properties to create (default:1)
+
descriptionSize
the size (bytes) of each cm:description property (default:128)
+
+ ]]>
+ /api/model/filefolder/load + + user + required + draft_public_api +
\ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index e55fa96d3f..7571b3c6bd 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1886,6 +1886,9 @@ + + + diff --git a/source/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java b/source/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java new file mode 100644 index 0000000000..ddaca49b46 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java @@ -0,0 +1,164 @@ +/* + * 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.web.scripts.model.filefolder; + +import java.io.IOException; +import java.io.OutputStream; + +import org.alfresco.repo.model.filefolder.FileFolderLoader; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.extensions.webscripts.AbstractWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; + +/** + * Link to {@link FileFolderLoader} + */ +public class FileFolderLoaderPost extends AbstractWebScript implements ApplicationContextAware +{ + public static final String KEY_FOLDER_PATH = "folderPath"; + public static final String KEY_FILE_COUNT = "fileCount"; + public static final String KEY_FILES_PER_TXN = "filesPerTxn"; + public static final String KEY_MIN_FILE_SIZE = "minFileSize"; + public static final String KEY_MAX_FILE_SIZE = "maxFileSize"; + public static final String KEY_MAX_UNIQUE_DOCUMENTS = "maxUniqueDocuments"; + public static final String KEY_FORCE_BINARY_STORAGE = "forceBinaryStorage"; + public static final String KEY_DESCRIPTION_COUNT = "descriptionCount"; + public static final String KEY_DESCRIPTION_SIZE = "descriptionSize"; + public static final String KEY_COUNT = "count"; + + public static final int DEFAULT_FILE_COUNT = 100; + public static final int DEFAULT_FILES_PER_TXN = 100; + public static final long DEFAULT_MIN_FILE_SIZE = 80*1024L; + public static final long DEFAULT_MAX_FILE_SIZE = 120*1024L; + public static final long DEFAULT_MAX_UNIQUE_DOCUMENTS = Long.MAX_VALUE; + public static final int DEFAULT_DESCRIPTION_COUNT = 1; + public static final long DEFAULT_DESCRIPTION_SIZE = 128L; + public static final boolean DEFAULT_FORCE_BINARY_STORAGE = false; + + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + FileFolderLoader loader = (FileFolderLoader) applicationContext.getBean("fileFolderLoader"); + + int count = 0; + String folderPath = ""; + try + { + JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent())); + folderPath = json.getString(KEY_FOLDER_PATH); + if (folderPath == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, KEY_FOLDER_PATH + " not supplied."); + } + int fileCount = 100; + if (json.has(KEY_FILE_COUNT)) + { + fileCount = json.getInt(KEY_FILE_COUNT); + } + int filesPerTxn = DEFAULT_FILES_PER_TXN; + if (json.has(KEY_FILES_PER_TXN)) + { + filesPerTxn = json.getInt(KEY_FILES_PER_TXN); + } + long minFileSize = DEFAULT_MIN_FILE_SIZE; + if (json.has(KEY_MIN_FILE_SIZE)) + { + minFileSize = json.getInt(KEY_MIN_FILE_SIZE); + } + long maxFileSize = DEFAULT_MAX_FILE_SIZE; + if (json.has(KEY_MAX_FILE_SIZE)) + { + maxFileSize = json.getInt(KEY_MAX_FILE_SIZE); + } + long maxUniqueDocuments = DEFAULT_MAX_UNIQUE_DOCUMENTS; + if (json.has(KEY_MAX_UNIQUE_DOCUMENTS)) + { + maxUniqueDocuments = json.getInt(KEY_MAX_UNIQUE_DOCUMENTS); + } + boolean forceBinaryStorage = DEFAULT_FORCE_BINARY_STORAGE; + if (json.has(KEY_FORCE_BINARY_STORAGE)) + { + forceBinaryStorage = json.getBoolean(KEY_FORCE_BINARY_STORAGE); + } + int descriptionCount = DEFAULT_DESCRIPTION_COUNT; + if (json.has(KEY_DESCRIPTION_COUNT)) + { + descriptionCount = json.getInt(KEY_DESCRIPTION_COUNT); + } + long descriptionSize = DEFAULT_DESCRIPTION_SIZE; + if (json.has(KEY_DESCRIPTION_SIZE)) + { + descriptionSize = json.getLong(KEY_DESCRIPTION_SIZE); + } + + // Perform the load + count = loader.createFiles( + folderPath, + fileCount, filesPerTxn, + minFileSize, maxFileSize, + maxUniqueDocuments, + forceBinaryStorage, + descriptionCount, descriptionSize); + } + catch (FileNotFoundException e) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Folder not found: ", folderPath); + } + catch (IOException iox) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); + } + // Write the response + OutputStream os = res.getOutputStream(); + try + { + JSONObject json = new JSONObject(); + json.put(KEY_COUNT, count); + os.write(json.toString().getBytes("UTF-8")); + } + catch (JSONException e) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e); + } + finally + { + os.close(); + } + } +} diff --git a/source/test-java/org/alfresco/repo/model/filefolder/RemoteFileFolderLoaderTest.java b/source/test-java/org/alfresco/repo/model/filefolder/RemoteFileFolderLoaderTest.java new file mode 100644 index 0000000000..98d360eb60 --- /dev/null +++ b/source/test-java/org/alfresco/repo/model/filefolder/RemoteFileFolderLoaderTest.java @@ -0,0 +1,252 @@ +/* + * 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 java.util.List; +import java.util.Locale; +import java.util.UUID; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.content.filestore.SpoofedTextContentReader; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.repo.web.scripts.model.filefolder.FileFolderLoaderPost; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.json.simple.JSONObject; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Remote FileFolderLoader testing + * + * @author Derek Hulley + * @since 5.1 + */ +public class RemoteFileFolderLoaderTest extends BaseWebScriptTest +{ + public static final String URL = "/api/model/filefolder/load"; + + private Repository repositoryHelper; + private NodeService nodeService; + private TransactionService transactionService; + private FileFolderService fileFolderService; + private String sharedHomePath; + private NodeRef loadHomeNodeRef; + private String loadHomePath; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + this.repositoryHelper = (Repository)getServer().getApplicationContext().getBean("repositoryHelper"); + this.nodeService = (NodeService)getServer().getApplicationContext().getBean("nodeService"); + this.transactionService = (TransactionService) getServer().getApplicationContext().getBean("TransactionService"); + this.fileFolderService = (FileFolderService) getServer().getApplicationContext().getBean("FileFolderService"); + + // Get the path of the shared folder home + final NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome(); + final NodeRef sharedHomeNodeRef = repositoryHelper.getSharedHome(); + RetryingTransactionCallback createFolderWork = new RetryingTransactionCallback() + { + @Override + public NodeRef execute() throws Throwable + { + List sharedHomeFileInfos = fileFolderService.getNamePath(companyHomeNodeRef, sharedHomeNodeRef); + sharedHomePath = "/" + sharedHomeFileInfos.get(0).getName(); + + String folderName = UUID.randomUUID().toString(); + // Create a folder + FileInfo folderInfo = fileFolderService.create(sharedHomeNodeRef, folderName, ContentModel.TYPE_FOLDER); + loadHomePath = sharedHomePath + "/" + folderName; + // Done + return folderInfo.getNodeRef(); + } + }; + AuthenticationUtil.pushAuthentication(); // Will be cleared later + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + + loadHomeNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(createFolderWork); + } + + @Override + protected void tearDown() throws Exception + { + RetryingTransactionCallback deleteFolderWork = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + fileFolderService.delete(loadHomeNodeRef); + // Done + return null; + } + }; + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(deleteFolderWork); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + + /** + * Load with no folder path + */ + public void testLoad_noFolderPath() throws Exception + { + JSONObject body = new JSONObject(); + + sendRequest( + new PostRequest(URL, body.toString(), "application/json"), + Status.STATUS_BAD_REQUEST, + "bmarley"); + } + + /** + * Load with defaults + */ + @SuppressWarnings("unchecked") + public void testLoad_default_default() throws Exception + { + JSONObject body = new JSONObject(); + body.put(FileFolderLoaderPost.KEY_FOLDER_PATH, loadHomePath); + + Response response = sendRequest( + new PostRequest(URL, body.toString(), "application/json"), + Status.STATUS_OK, + "bmarley"); + assertEquals("{\"count\":100}", response.getContentAsString()); + + // Check file(s) + assertEquals(100, nodeService.countChildAssocs(loadHomeNodeRef, true)); + } + + /** + * Load 15 files with default sizes + */ + @SuppressWarnings("unchecked") + public void testLoad_15_default() throws Exception + { + JSONObject body = new JSONObject(); + body.put(FileFolderLoaderPost.KEY_FOLDER_PATH, loadHomePath); + body.put(FileFolderLoaderPost.KEY_FILE_COUNT, 15); + body.put(FileFolderLoaderPost.KEY_FILES_PER_TXN, 10); + + Response response = null; + try + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser("hhoudini"); + response = sendRequest( + new PostRequest(URL, body.toString(), "application/json"), + Status.STATUS_OK, + "hhoudini"); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + assertEquals("{\"count\":15}", response.getContentAsString()); + + // Check file(s) + assertEquals(15, nodeService.countChildAssocs(loadHomeNodeRef, true)); + // Size should be default + List fileInfos = fileFolderService.list(loadHomeNodeRef); + for (FileInfo fileInfo : fileInfos) + { + NodeRef fileNodeRef = fileInfo.getNodeRef(); + ContentReader reader = fileFolderService.getReader(fileNodeRef); + // Expect spoofing by default + assertTrue(reader.getContentUrl().startsWith(FileContentStore.SPOOF_PROTOCOL)); + assertTrue( + "Default file size not correct: " + reader, + FileFolderLoaderPost.DEFAULT_MIN_FILE_SIZE < reader.getSize() && + reader.getSize() < FileFolderLoaderPost.DEFAULT_MAX_FILE_SIZE); + // Check creator + assertEquals("hhoudini", nodeService.getProperty(fileNodeRef, ContentModel.PROP_CREATOR)); + // We also expect the default language description to be present + String description = (String) nodeService.getProperty(fileNodeRef, ContentModel.PROP_DESCRIPTION); + assertNotNull("No description", description); + assertEquals("Description length incorrect: ", 128L, description.getBytes("UTF-8").length); + } + } + + /** + * Load 15 files; 1K size; 1 document sample; force binary storage + */ + @SuppressWarnings("unchecked") + public void testLoad_15_16bytes() throws Exception + { + JSONObject body = new JSONObject(); + body.put(FileFolderLoaderPost.KEY_FOLDER_PATH, loadHomePath); + body.put(FileFolderLoaderPost.KEY_MIN_FILE_SIZE, 16L); + body.put(FileFolderLoaderPost.KEY_MAX_FILE_SIZE, 16L); + body.put(FileFolderLoaderPost.KEY_MAX_UNIQUE_DOCUMENTS, 1L); + body.put(FileFolderLoaderPost.KEY_FORCE_BINARY_STORAGE, Boolean.TRUE); + + Response response = null; + try + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser("maggi"); + response = sendRequest( + new PostRequest(URL, body.toString(), "application/json"), + Status.STATUS_OK, + "maggi"); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + assertEquals("{\"count\":100}", response.getContentAsString()); + + // Check file(s) + assertEquals(100, nodeService.countChildAssocs(loadHomeNodeRef, true)); + + // Consistent binary text + String contentUrlCheck = SpoofedTextContentReader.createContentUrl(Locale.ENGLISH, 0L, 16L); + ContentReader readerCheck = new SpoofedTextContentReader(contentUrlCheck); + String textCheck = readerCheck.getContentString(); + + // Size should be default + List fileInfos = fileFolderService.list(loadHomeNodeRef); + for (FileInfo fileInfo : fileInfos) + { + NodeRef fileNodeRef = fileInfo.getNodeRef(); + ContentReader reader = fileFolderService.getReader(fileNodeRef); + // Expect storage in store + assertTrue(reader.getContentUrl().startsWith(FileContentStore.STORE_PROTOCOL)); + // Check text + String text = reader.getContentString(); + assertEquals("Text not the same.", textCheck, text); + } + } +} diff --git a/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java b/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java index 04ef309aaf..47fa3f530f 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java +++ b/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java @@ -18,12 +18,8 @@ */ package org.alfresco.repo.web.scripts; -import static org.mockito.Matchers.any; -import static org.springframework.extensions.webscripts.Status.STATUS_OK; - import java.sql.SQLException; import java.util.Arrays; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; @@ -52,6 +48,9 @@ import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptResponse; +import static org.mockito.Matchers.any; +import static org.springframework.extensions.webscripts.Status.STATUS_OK; + /** * Unit test to test runas function * diff --git a/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java b/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java index c3e83ba0ad..2020c9dd6a 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java +++ b/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java @@ -21,6 +21,7 @@ package org.alfresco.repo.web.scripts; import junit.framework.Test; import junit.framework.TestSuite; +import org.alfresco.repo.model.filefolder.RemoteFileFolderLoaderTest; import org.alfresco.repo.web.scripts.action.RunningActionRestApiTest; import org.alfresco.repo.web.scripts.activities.feed.control.FeedControlTest; import org.alfresco.repo.web.scripts.admin.AdminWebScriptTest; @@ -107,7 +108,7 @@ public class WebScriptTestSuite extends TestSuite suite.addTestSuite( SlingshotContentGetTest.class); suite.addTestSuite( XssVulnerabilityTest.class ); suite.addTestSuite( LinksRestApiTest.class ); - + suite.addTestSuite( RemoteFileFolderLoaderTest.class ); // This uses a slightly different context // As such, we can't run it in the same suite as the others, // due to finalisers closing caches when we're not looking