diff --git a/amps/ags/pom.xml b/amps/ags/pom.xml index af9673b898..71ccd59529 100644 --- a/amps/ags/pom.xml +++ b/amps/ags/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-amps - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/ags/rm-automation/pom.xml b/amps/ags/rm-automation/pom.xml index c9989c4898..e87874300c 100644 --- a/amps/ags/rm-automation/pom.xml +++ b/amps/ags/rm-automation/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-parent - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml index 220edbd446..108dcc2b3a 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-automation-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java new file mode 100644 index 0000000000..12d322fdb2 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/ExportAPI.java @@ -0,0 +1,103 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.v0; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.alfresco.rest.core.v0.BaseAPI; +import org.apache.http.HttpResponse; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +/** + * Methods to make API requests using v0 API for Exporting Items + * + * @author Shubham Jain + * @since 7.1.0 + */ + +@Component +public class ExportAPI extends BaseAPI +{ + /** + * The URI to export an item + */ + private static final String EXPORT_API = "{0}rma/admin/export"; + + /** + * Export a single Record/Record Folder/Record Category using V0 Export API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeID ID of the Node(Record/RecordFolder) to be exported + * @return HTTP Response + */ + public HttpResponse exportRMNode(String user, String password, int expectedStatusCode, String nodeID) + { + return export(user, password, expectedStatusCode, Collections.singletonList(getNodeRefSpacesStore() + nodeID)); + } + + /** + * Export a list of nodes using V0 Export API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeIDList List of the nodes to be exported + * @return HTTP Response + */ + public HttpResponse exportRMNodes(String user, String password, int expectedStatusCode, List nodeIDList) + { + + List nodeRefs = + nodeIDList.stream().map(nodeID -> getNodeRefSpacesStore() + nodeID).collect(Collectors.toList()); + + return export(user, password, expectedStatusCode, nodeRefs); + } + + /** + * Export API function to perform Export Operation on items with given noderefs using V0 Export Rest API + * + * @param user User performing the export + * @param password User's Password + * @param expectedStatusCode Expected Response Code + * @param nodeRefs list of the noderefs for the items to be exported + * @return Rest API Post Request + */ + public HttpResponse export(String user, String password, int expectedStatusCode, List nodeRefs) + { + final JSONObject requestParams = new JSONObject(); + + requestParams.put("nodeRefs", new JSONArray(nodeRefs)); + + return doPostJsonRequest(user, password, expectedStatusCode, requestParams, EXPORT_API); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java new file mode 100644 index 0000000000..d3e7bde53f --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/records/ExportRecordsTests.java @@ -0,0 +1,136 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.records; + +import static java.util.Arrays.asList; + +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.CONTENT_TYPE; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createTempFile; +import static org.alfresco.utility.data.RandomData.getRandomName; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.apache.http.HttpStatus.SC_OK; + +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.record.Record; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; +import org.alfresco.rest.v0.ExportAPI; +import org.alfresco.test.AlfrescoTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * This class contains tests for testing the Export functionality on RM site + * + * @author Shubham Jain + * @since 7.1.0 + */ +public class ExportRecordsTests extends BaseRMRestTest +{ + private RecordCategory rootCategory; + + private RecordCategoryChild recordFolder; + + @Autowired + private ExportAPI exportAPI; + + @BeforeClass (alwaysRun = true) + public void exportRecordsTestsBeforeClass() + { + STEP("Create root level category"); + rootCategory = createRootCategory(getRandomName("Category")); + + STEP("Create the record folder inside the rootCategory"); + recordFolder = createRecordFolder(rootCategory.getId(), getRandomName("Folder")); + + } + + @DataProvider (name = "CreateRMNodes") + public Object[][] getRMNodeID() + { + return new String[][] { + { createRecord("Record_4MB", 4).getId() }, + { createRecord("Record_200MB", 200).getId() }, + { recordFolder.getId() } + }; + } + + /** + * Given a record with size > 4 MB + * When I export the record using API + * Then the request is successful + */ + @Test (description = "Testing the RM Export functionality for records of size >4MB and Record " + + "Folder containing records with size >4MB", + dataProvider = "CreateRMNodes") + @AlfrescoTest (jira = "APPS-986") + public void exportRMNodeTest(String nodeID) + { + STEP("Export the created record/record folder with size greater than 4 MB and verifying the expected response" + + " code"); + exportAPI.exportRMNode(getAdminUser().getUsername(), getAdminUser().getPassword(), SC_OK, nodeID); + } + + /** + * I would change this to + * Given a list of records with a size > 4MB + * When I export the records + * Then the request is succesfull + */ + @Test (description = "Testing the RM Export functionality using API for a list of Records at once with " + + "collective size of more than 4MB") + public void exportRecordsTest() + { + STEP("Export all the created records at once and verifying the expected response code"); + exportAPI.exportRMNodes(getAdminUser().getUsername(), getAdminUser().getPassword(), + SC_OK, asList(createRecord("Record_2MB", 2).getId(), createRecord("Record_3MB", 3).getId())); + } + + /** + * Create a Record with a specific size in RM Site inside already created Record Folder + * + * @param recordName Name of the record to be created + * @param sizeInMegaBytes Size of the record to be created in MegaBytes + * @return Created record with defined size + */ + public Record createRecord(String recordName, int sizeInMegaBytes) + { + return getRestAPIFactory().getRecordFolderAPI().createRecord(Record.builder().name(recordName) + .nodeType(CONTENT_TYPE).build(), recordFolder.getId(), + createTempFile("TempFile", sizeInMegaBytes)); + } + + @AfterClass (alwaysRun = true) + public void exportRecordsTestsAfter() + { + STEP("Delete the created rootCategory along with corresponding record folders/records present in it"); + getRestAPIFactory().getRecordCategoryAPI().deleteRecordCategory(rootCategory.getId()); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java index 56dd5da2bb..fe91643217 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/utils/FilePlanComponentsUtil.java @@ -41,6 +41,7 @@ import static org.testng.Assert.assertTrue; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; +import java.io.RandomAccessFile; import org.alfresco.rest.rm.community.model.record.Record; import org.alfresco.rest.rm.community.model.record.RecordProperties; @@ -66,13 +67,19 @@ public class FilePlanComponentsUtil // Intentionally blank } - /** Name of the image resource file to be used for records body */ + /** + * Name of the image resource file to be used for records body + */ public static final String IMAGE_FILE = "money.JPG"; - /** Title prefix for record category children */ + /** + * Title prefix for record category children + */ public static final String TITLE_PREFIX = "Title for "; - /** Description prefix for record category children */ + /** + * Description prefix for record category children + */ public static final String DESCRIPTION_PREFIX = "This is the description for"; @@ -87,7 +94,7 @@ public class FilePlanComponentsUtil } /** - * Creates a record model with the given type and a random name (with "Record " prefix) + * Creates a record model with the given type and a random name (with "Record " prefix) * * @param nodeType The node type * @return The {@link Record} with for the given node type @@ -95,9 +102,9 @@ public class FilePlanComponentsUtil private static Record createRecordModel(String nodeType) { return Record.builder() - .name("Record " + getRandomAlphanumeric()) - .nodeType(nodeType) - .build(); + .name("Record " + getRandomAlphanumeric()) + .nodeType(nodeType) + .build(); } /** @@ -133,22 +140,22 @@ public class FilePlanComponentsUtil /** * Creates an unfiled records container child record model with the given name and type * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ public static UnfiledContainerChild createUnfiledContainerChildRecordModel(String name, String nodeType) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(nodeType) - .build(); + .name(name) + .nodeType(nodeType) + .build(); } /** * Creates a nonElectronic container child record model with all available properties for the non electronic records * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ @@ -156,19 +163,19 @@ public class FilePlanComponentsUtil String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(NON_ELECTRONIC_RECORD_TYPE) - .properties(UnfiledContainerChildProperties.builder() - .title(title) - .description(description) - .box(box) - .file(file) - .shelf(shelf) - .storageLocation(storageLocation) - .numberOfCopies(numberOfCopies) - .physicalSize(physicalSize) - .build()) - .build(); + .name(name) + .nodeType(NON_ELECTRONIC_RECORD_TYPE) + .properties(UnfiledContainerChildProperties.builder() + .title(title) + .description(description) + .box(box) + .file(file) + .shelf(shelf) + .storageLocation(storageLocation) + .numberOfCopies(numberOfCopies) + .physicalSize(physicalSize) + .build()) + .build(); } /** @@ -190,110 +197,110 @@ public class FilePlanComponentsUtil String shelf, String storageLocation, Integer numberOfCopies, Integer physicalSize) { return Record.builder() - .name(name) - .nodeType(NON_ELECTRONIC_RECORD_TYPE) - .properties(RecordProperties.builder() - .title(title) - .description(description) - .box(box) - .file(file) - .shelf(shelf) - .storageLocation(storageLocation) - .numberOfCopies(numberOfCopies) - .physicalSize(physicalSize) - .build()) - .build(); + .name(name) + .nodeType(NON_ELECTRONIC_RECORD_TYPE) + .properties(RecordProperties.builder() + .title(title) + .description(description) + .box(box) + .file(file) + .shelf(shelf) + .storageLocation(storageLocation) + .numberOfCopies(numberOfCopies) + .physicalSize(physicalSize) + .build()) + .build(); } /** * Creates a record model with the given name, description and title * - * @param name The name of the record + * @param name The name of the record * @param description The description of the record - * @param title The title of the record + * @param title The title of the record * @return The {@link Record} with the given details */ public static Record createRecordModel(String name, String description, String title) { return Record.builder() - .name(name) - .properties(RecordProperties.builder() - .description(description) - .title(title) - .build()) - .build(); + .name(name) + .properties(RecordProperties.builder() + .description(description) + .title(title) + .build()) + .build(); } /** * Creates a record category child model with the given name and type * - * @param name The name of the record category child + * @param name The name of the record category child * @param nodeType The type of the record category child * @return The {@link RecordCategoryChild} with the given details */ public static RecordCategoryChild createRecordCategoryChildModel(String name, String nodeType) { return RecordCategoryChild.builder() - .name(name) - .nodeType(nodeType) - .properties(RecordCategoryChildProperties.builder() - .title(TITLE_PREFIX + name) - .build()) - .build(); + .name(name) + .nodeType(nodeType) + .properties(RecordCategoryChildProperties.builder() + .title(TITLE_PREFIX + name) + .build()) + .build(); } /** * Creates a record category model with the given name and title * - * @param name The name of the record category + * @param name The name of the record category * @param title The title of the record category * @return The {@link RecordCategory} with the given details */ public static RecordCategory createRecordCategoryModel(String name, String title) { return RecordCategory.builder() - .name(name) - .nodeType(RECORD_CATEGORY_TYPE) - .properties(RecordCategoryProperties.builder() - .title(title) - .build()) - .build(); + .name(name) + .nodeType(RECORD_CATEGORY_TYPE) + .properties(RecordCategoryProperties.builder() + .title(title) + .build()) + .build(); } /** * Creates a record folder model with the given name and title * - * @param name The name of the record folder + * @param name The name of the record folder * @param title The title of the record folder * @return The {@link RecordFolder} with the given details */ public static RecordFolder createRecordFolderModel(String name, String title) { return RecordFolder.builder() - .name(name) - .nodeType(RECORD_FOLDER_TYPE) - .properties(RecordFolderProperties.builder() - .title(title) - .build()) - .build(); + .name(name) + .nodeType(RECORD_FOLDER_TYPE) + .properties(RecordFolderProperties.builder() + .title(title) + .build()) + .build(); } /** * Creates an unfiled records container child model with the given name and type * - * @param name The name of the unfiled records container child + * @param name The name of the unfiled records container child * @param nodeType The type of the record category child * @return The {@link UnfiledContainerChild} with the given details */ public static UnfiledContainerChild createUnfiledContainerChildModel(String name, String nodeType) { return UnfiledContainerChild.builder() - .name(name) - .nodeType(nodeType) - .properties(UnfiledContainerChildProperties.builder() - .title(TITLE_PREFIX + name) - .build()) - .build(); + .name(name) + .nodeType(nodeType) + .properties(UnfiledContainerChildProperties.builder() + .title(TITLE_PREFIX + name) + .build()) + .build(); } /** @@ -324,6 +331,32 @@ public class FilePlanComponentsUtil } } + /** + * Method to create a temporary file with specific size + * + * @param name file name + * @param sizeInMegaBytes size + * @return temporary file + */ + public static File createTempFile(final String name, long sizeInMegaBytes) + { + try + { + // Create file + final File file = File.createTempFile(name, ".txt"); + + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + raf.setLength(sizeInMegaBytes * 1024 * 1024); + raf.close(); + + return file; + } + catch (Exception exception) + { + throw new RuntimeException("Unable to create test file.", exception); + } + } + /** * Helper method to verify all properties of a nonElectronic record * diff --git a/amps/ags/rm-community/pom.xml b/amps/ags/rm-community/pom.xml index 842cc07794..9472d3f9de 100644 --- a/amps/ags/rm-community/pom.xml +++ b/amps/ags/rm-community/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-parent - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml index 8218bd49d0..23b950f2ea 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml @@ -102,9 +102,11 @@ + + diff --git a/amps/ags/rm-community/rm-community-repo/pom.xml b/amps/ags/rm-community/rm-community-repo/pom.xml index d298cb58bd..0f25658ed2 100644 --- a/amps/ags/rm-community/rm-community-repo/pom.xml +++ b/amps/ags/rm-community/rm-community-repo/pom.xml @@ -8,13 +8,13 @@ org.alfresco alfresco-governance-services-community-repo-parent - 11.95-SNAPSHOT + 11.105-SNAPSHOT ${project.build.directory}/${project.build.finalName}-war - alfresco/alfresco-governance-repository-community + alfresco/alfresco-governance-repository-community-base diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java index bd0d9e17dd..a064d97c38 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/AbstractDisposableItem.java @@ -30,6 +30,10 @@ package org.alfresco.module.org_alfresco_module_rm.model.behaviour; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; +import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; @@ -52,7 +56,13 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean /** disposition service */ protected DispositionService dispositionService; - + + /** record service */ + protected RecordService recordService; + + /** record folder service */ + protected RecordFolderService recordFolderService; + /** * @param dispositionService disposition service */ @@ -60,6 +70,22 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean { this.dispositionService = dispositionService; } + + /** + * @param recordService record service + */ + public void setRecordService(RecordService recordService) + { + this.recordService = recordService; + } + + /** + * @param recordFolderService record folder service + */ + public void setRecordFolderService(RecordFolderService recordFolderService) + { + this.recordFolderService = recordFolderService; + } /** * Removes unwanted aspects @@ -86,4 +112,35 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean } } + /** + * Cleans and re-initiates the containing records + * + * @param childAssociationRef + */ + protected void reinitializeRecordFolder(ChildAssociationRef childAssociationRef) + { + + NodeRef newNodeRef = childAssociationRef.getChildRef(); + + AuthenticationUtil.runAs(() -> { + // clean record folder + cleanDisposableItem(nodeService, newNodeRef); + + // re-initialise the record folder + recordFolderService.setupRecordFolder(newNodeRef); + + // sort out the child records + for (NodeRef record : recordService.getRecords(newNodeRef)) + { + // clean record + cleanDisposableItem(nodeService, record); + + // Re-initiate the records in the new folder. + recordService.file(record); + } + + return null; + }, AuthenticationUtil.getSystemUserName()); + } + } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java index 500aec6f6d..9a09513f15 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordCategoryType.java @@ -27,13 +27,14 @@ package org.alfresco.module.org_alfresco_module_rm.model.rma.type; +import static org.alfresco.model.ContentModel.TYPE_CONTENT; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.alfresco.model.ContentModel; -import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; -import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; +import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem; import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService; import org.alfresco.repo.copy.CopyBehaviourCallback; @@ -49,6 +50,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; /** * rma:recordCategory behaviour bean @@ -60,9 +62,10 @@ import org.alfresco.service.namespace.QName; ( defaultType = "rma:recordCategory" ) -public class RecordCategoryType extends BaseBehaviourBean +public class RecordCategoryType extends AbstractDisposableItem implements NodeServicePolicies.OnCreateChildAssociationPolicy, - NodeServicePolicies.OnCreateNodePolicy + NodeServicePolicies.OnCreateNodePolicy, + NodeServicePolicies.OnMoveNodePolicy { private final static List ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>(); private final static List ACCEPTED_NON_UNIQUE_CHILD_TYPES = Arrays.asList(TYPE_RECORD_CATEGORY, TYPE_RECORD_FOLDER); @@ -73,9 +76,6 @@ public class RecordCategoryType extends BaseBehaviourBean /** file plan permission service */ protected FilePlanPermissionService filePlanPermissionService; - /** record folder service */ - private RecordFolderService recordFolderService; - /** * @param vitalRecordService vital record service */ @@ -92,14 +92,6 @@ public class RecordCategoryType extends BaseBehaviourBean this.filePlanPermissionService = filePlanPermissionService; } - /** - * @param recordFolderService record folder service - */ - public void setRecordFolderService(RecordFolderService recordFolderService) - { - this.recordFolderService = recordFolderService; - } - /** * On every event * @@ -204,6 +196,53 @@ public class RecordCategoryType extends BaseBehaviourBean } + /** + * Record Category move behaviour + * + * @see org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy#onMoveNode(org.alfresco.service.cmr.repository.ChildAssociationRef, org.alfresco.service.cmr.repository.ChildAssociationRef) + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS, + notificationFrequency = NotificationFrequency.FIRST_EVENT + ) + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + // clean the child folders and records only if the old parent category has a disposition schedule set + // if it doesn't, then there are no old properties on the child nodes that have to be cleaned in order + // for new ones to be set + if (nodeService.getType(newChildAssocRef.getChildRef()).equals(TYPE_RECORD_CATEGORY) + && dispositionService.getDispositionSchedule(oldChildAssocRef.getParentRef()) != null) + { + reinitializeRecordFolders(newChildAssocRef); + } + } + + /** + * Recursively reinitialize each folder in a structure of categories + * Unwanted aspects will be removed from the child records and the records will be re-filed + * Disposition schedule aspects and properties will be inherited from the new parent category + * + * @param childAssociationRef + */ + private void reinitializeRecordFolders(ChildAssociationRef childAssociationRef) + { + for (ChildAssociationRef newChildRef : nodeService.getChildAssocs(childAssociationRef.getChildRef(), + ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL)) + { + if (nodeService.getType(newChildRef.getChildRef()).equals(TYPE_RECORD_CATEGORY)) + { + reinitializeRecordFolders(newChildRef); + } + else if (!nodeService.getType(newChildRef.getChildRef()).equals(TYPE_CONTENT)) + { + reinitializeRecordFolder(newChildRef); + } + } + } + /** * Copy callback for record category */ diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java index f9eb29dbfb..f6dee11ea2 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/type/RecordFolderType.java @@ -34,8 +34,6 @@ import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.identifier.IdentifierService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem; -import org.alfresco.module.org_alfresco_module_rm.record.RecordService; -import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; @@ -68,11 +66,6 @@ public class RecordFolderType extends AbstractDisposableItem implements NodeServicePolicies.OnMoveNodePolicy, NodeServicePolicies.OnCreateChildAssociationPolicy { - /** record service */ - private RecordService recordService; - - /** record folder service */ - private RecordFolderService recordFolderService; /** vital record service */ protected VitalRecordService vitalRecordService; @@ -85,22 +78,6 @@ public class RecordFolderType extends AbstractDisposableItem private static final String MSG_CANNOT_CREATE_CHILDREN_IN_CLOSED_RECORD_FOLDER = "rm.service.add-children-to-closed-record-folder"; - /** - * @param recordService record service - */ - public void setRecordService(RecordService recordService) - { - this.recordService = recordService; - } - - /** - * @param recordFolderService record folder service - */ - public void setRecordFolderService(RecordFolderService recordFolderService) - { - this.recordFolderService = recordFolderService; - } - /** * @param vitalRecordService vital record service */ @@ -131,31 +108,7 @@ public class RecordFolderType extends AbstractDisposableItem { if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef())) { - final NodeRef newNodeRef = newChildAssocRef.getChildRef(); - - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() - { - // clean record folder - cleanDisposableItem(nodeService, newNodeRef); - - // re-initialise the record folder - recordFolderService.setupRecordFolder(newNodeRef); - - // sort out the child records - for (NodeRef record : recordService.getRecords(newNodeRef)) - { - // clean record - cleanDisposableItem(nodeService, record); - - // Re-initiate the records in the new folder. - recordService.file(record); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); + reinitializeRecordFolder(newChildAssocRef); } } else diff --git a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java index e55fe0e7b6..2427227972 100644 --- a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java +++ b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/disposition/DispositionScheduleInheritanceTest.java @@ -28,6 +28,8 @@ package org.alfresco.module.org_alfresco_module_rm.test.integration.disposition; import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_DESCRIPTION; import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS; +import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_EVENT_NAME; +import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.SEPARATION_EVENT_NAME; import static org.alfresco.util.GUID.generate; import java.io.Serializable; @@ -37,6 +39,7 @@ import java.util.List; import java.util.Map; import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.RetainAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; @@ -190,6 +193,162 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase }); } + /** + * Given a root record category A with a retention schedule set to retain and destroy after 1 day + * and another root record category B with a retention schedule set to cut off and destroy after 1 day containing a + * subcategory + * When moving the subcategory into the first root category + * Then records under the subcategory inherit the retention schedule of the parent record category + * The events list contain the retain event step inherited from the new parent category + *

+ * Please see https://alfresco.atlassian.net/browse/APPS-1004 + */ + public void testRetentionScheduleInheritance_APPS_1004() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + NodeRef category1; + NodeRef subcategory2; + NodeRef record; + Date asOfDateBeforeMove; + + @Override + public void given() + { + // create root category1 + category1 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category1 + createDispositionScheduleRetainAndCutOffOneDay(category1); + + // create root category2 + NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category2 + createDispositionScheduleCutOffAndDestroyOneDay(category2); + + // create subcategory2 under category2 + subcategory2 = filePlanService.createRecordCategory(category2, generate()); + + // create folder under subcategory2 + folder = recordFolderService.createRecordFolder(subcategory2, generate()); + + // file record in folder and complete it + record = utils.createRecord(folder, generate(), generate()); + utils.completeRecord(record); + + //store the date to check if it was updated + asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate(); + } + + @Override + public void when() throws Exception + { + // move subcategory2 under category1 + fileFolderService.move(subcategory2, category1, null); + } + + @Override + public void then() throws Exception + { + dispositionService.getDispositionSchedule(record); + // check the next disposition action + DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record); + assertNotNull(dispositionActionAfterMove); + assertEquals(RetainAction.NAME, dispositionActionAfterMove.getName()); + assertNotNull(dispositionActionAfterMove.getAsOfDate()); + assertTrue(dispositionActionAfterMove.getAsOfDate().after(asOfDateBeforeMove)); + + // check the search aspect details + assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH)); + assertEquals(RetainAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_AS_OF)); + assertNull((List) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY)); + assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE)); + } + }); + } + + /** + * Given a root record category A with a retention schedule set to cut off on event 'case closed' + * and another root record category B with a retention schedule set to cut off on event 'separation' + * When moving the subcategory into the first root category + * Then records under the subcategory inherit the retention schedule of the parent record category + * The events list contain the case closed event step inherited from the new parent category + *

+ * Please see https://alfresco.atlassian.net/browse/APPS-1005 + */ + public void testRetentionScheduleInheritance_APPS_1005() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + NodeRef category1; + NodeRef subcategory2; + NodeRef record; + Date asOfDateBeforeMove; + + @Override + public void given() + { + // create root category1 + category1 = filePlanService.createRecordCategory(filePlan, generate()); + + utils.createDispositionSchedule(category1, DEFAULT_DISPOSITION_INSTRUCTIONS, + DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, DEFAULT_EVENT_NAME); + + // create root category2 + NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate()); + + // create record level disposition schedule for category2 + utils.createDispositionSchedule(category2, DEFAULT_DISPOSITION_INSTRUCTIONS, + DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, SEPARATION_EVENT_NAME); + + // create subcategory2 under category2 + subcategory2 = filePlanService.createRecordCategory(category2, generate()); + + // create folder under subcategory2 + folder = recordFolderService.createRecordFolder(subcategory2, generate()); + + // file record in folder and complete it + record = utils.createRecord(folder, generate(), generate()); + utils.completeRecord(record); + + //store the date to check if it was updated + asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate(); + } + + @Override + public void when() throws Exception + { + // move subcategory2 under category1 + fileFolderService.move(subcategory2, category1, null); + } + + @Override + public void then() throws Exception + { + // check the next disposition action + DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record); + assertNotNull(dispositionActionAfterMove); + assertEquals(CutOffAction.NAME, dispositionActionAfterMove.getName()); + + // check the search aspect details + assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH)); + assertEquals(CutOffAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME)); + assertNotNull((List) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS)); + assertEquals(((List) ((List) nodeService.getProperty(record, + PROP_RS_DISPOSITION_EVENTS))).size(), 1); + assertEquals(DEFAULT_EVENT_NAME, ((List) ((List) nodeService.getProperty(record, + PROP_RS_DISPOSITION_EVENTS))).get(0)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS)); + assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY)); + assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE)); + } + }); + } + private void createDispositionScheduleCutOff(NodeRef category, String action, String period) { DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); @@ -205,6 +364,22 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_IMMEDIATELY); } + private void createDispositionScheduleRetainAndCutOffOneDay(NodeRef category) + { + DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); + + createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + } + + private void createDispositionScheduleCutOffAndDestroyOneDay(NodeRef category) + { + DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false); + + createDispositionScheduleStep(ds, CutOffAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY); + } + private void createDispositionScheduleStep(DispositionSchedule ds, String action, String period) { Map step = new HashMap(3); diff --git a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java index 2543f63225..7d9dc3d0ae 100644 --- a/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java +++ b/amps/ags/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java @@ -82,8 +82,10 @@ public class CommonRMTestUtils implements RecordsManagementModel public static final String DEFAULT_DISPOSITION_INSTRUCTIONS = "disposition instructions"; public static final String DEFAULT_DISPOSITION_DESCRIPTION = "disposition action description"; public static final String DEFAULT_EVENT_NAME = "case_closed"; + public static final String SEPARATION_EVENT_NAME = "separation"; public static final String PERIOD_NONE = "none|0"; public static final String PERIOD_IMMEDIATELY = "immediately|0"; + public static final String PERIOD_ONE_DAY = "day|1"; public static final String PERIOD_FIVE_DAYS = "day|5"; public static final String PERIOD_TEN_DAYS = "day|10"; public static final String PERIOD_ONE_WEEK = "week|1"; diff --git a/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml b/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml index f4c00e6e41..6da2790efe 100644 --- a/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml +++ b/amps/ags/rm-community/rm-community-rest-api-explorer/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-governance-services-community-repo-parent - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/pom.xml b/amps/pom.xml index 28db9340dd..19b0a73d46 100644 --- a/amps/pom.xml +++ b/amps/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/amps/share-services/pom.xml b/amps/share-services/pom.xml index 1a21908162..c3e8daf05d 100644 --- a/amps/share-services/pom.xml +++ b/amps/share-services/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-community-repo-amps - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/core/pom.xml b/core/pom.xml index ad2c229971..1c3c05df1c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/data-model/pom.xml b/data-model/pom.xml index aa3b6072b0..89f06d7bc9 100644 --- a/data-model/pom.xml +++ b/data-model/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java index ccded57adb..4344a15c87 100644 --- a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java +++ b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,6 +25,8 @@ */ package org.alfresco.repo.content; +import java.util.Date; + import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentIOException; @@ -32,8 +34,8 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; -import java.util.Date; /** * Provides low-level retrieval of content @@ -239,6 +241,58 @@ public interface ContentStore */ public boolean delete(String contentUrl); + /** + * Checks if the store supports the retrieving of direct access URLs. + * + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise + */ + default boolean isContentDirectUrlEnabled() + { + return false; + } + + /** + * Checks if the store supports the retrieving of a direct access URL for the given node. + * + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise + */ + default boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + return false; + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @return A direct access {@code URL} object for the content + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return requestContentDirectUrl(contentUrl, attachment, fileName, null); + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + throw new UnsupportedOperationException( + "Retrieving direct access URLs is not supported by this content store."); + } + /** * Gets a presigned URL to directly access a binary content. It is up to the actual store * implementation if it can fulfil this request with an expiry time or not. @@ -248,10 +302,11 @@ public interface ContentStore * @return A direct access URL object for a binary content * @throws UnsupportedOperationException if the store is unable to provide the information */ + @Deprecated default DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) { throw new UnsupportedOperationException( - "Retrieving direct access URLs is not supported by this content store."); + "Retrieving direct access URLs is not supported by this content store."); } /** @@ -259,6 +314,7 @@ public interface ContentStore * * @return true if direct access URLs retrieving is supported, false otherwise */ + @Deprecated default boolean isDirectAccessSupported() { return false; diff --git a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java index 0df9532f7a..365f441bb2 100644 --- a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java +++ b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -27,6 +27,7 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import org.alfresco.api.AlfrescoPublicApi; @@ -36,7 +37,8 @@ public class DirectAccessUrl implements Serializable private static final long serialVersionUID = -881676208224414139L; private String contentUrl; - private Date expiresAt; + private Date expiryTime; + private boolean attachment; public String getContentUrl() { @@ -48,13 +50,38 @@ public class DirectAccessUrl implements Serializable this.contentUrl = contentUrl; } - public Date getExpiresAt() + public Date getExpiryTime() { - return expiresAt; + return expiryTime; } - public void setExpiresAt(Date expiresAt) + public void setExpiryTime(Date expiryTime) { - this.expiresAt = expiresAt; + this.expiryTime = expiryTime; + } + + public boolean isAttachment() + { + return attachment; + } + + public void setAttachment(boolean attachment) + { + this.attachment = attachment; + } + + @Override public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + DirectAccessUrl that = (DirectAccessUrl) obj; + return attachment == that.attachment && Objects.equals(contentUrl, + that.contentUrl) && Objects.equals(expiryTime, that.expiryTime); + } + + @Override public int hashCode() + { + return Objects.hash(contentUrl, expiryTime, attachment); } } diff --git a/packaging/distribution/pom.xml b/packaging/distribution/pom.xml index 80e189dc1d..054428b6f5 100644 --- a/packaging/distribution/pom.xml +++ b/packaging/distribution/pom.xml @@ -9,6 +9,6 @@ org.alfresco alfresco-community-repo-packaging - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/docker-alfresco/pom.xml b/packaging/docker-alfresco/pom.xml index bf90de59dd..15d69ea770 100644 --- a/packaging/docker-alfresco/pom.xml +++ b/packaging/docker-alfresco/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-packaging - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/pom.xml b/packaging/pom.xml index e39ee413fe..c4360347b6 100644 --- a/packaging/pom.xml +++ b/packaging/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/pom.xml b/packaging/tests/pom.xml index bae3119757..d0a186b887 100644 --- a/packaging/tests/pom.xml +++ b/packaging/tests/pom.xml @@ -6,7 +6,7 @@ org.alfresco alfresco-community-repo-packaging - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/tas-cmis/pom.xml b/packaging/tests/tas-cmis/pom.xml index 2bfa4a548a..376cbd9348 100644 --- a/packaging/tests/tas-cmis/pom.xml +++ b/packaging/tests/tas-cmis/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java b/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java index 088955f88f..d5eaa2a79b 100644 --- a/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java +++ b/packaging/tests/tas-cmis/src/test/java/org/alfresco/cmis/CancelCheckOutTests.java @@ -90,7 +90,7 @@ public class CancelCheckOutTests extends CmisTest .cancelCheckOut(); } - @Test(groups = { TestGroup.REGRESSION, TestGroup.CMIS}) + @Test(groups = { TestGroup.NOT_SUPPORTED_ON_SINGLE_PIPELINE, TestGroup.REGRESSION, TestGroup.CMIS}) @TestRail(section = {"cmis-api"}, executionType= ExecutionType.REGRESSION, description = "Verify that cancel check out on document created with Versioning State CHECKED OUT deletes the document") public void cancelCheckOutOnDocWithVersioningStateCheckedOut() throws Exception diff --git a/packaging/tests/tas-email/pom.xml b/packaging/tests/tas-email/pom.xml index 901246958d..85f38bbf38 100644 --- a/packaging/tests/tas-email/pom.xml +++ b/packaging/tests/tas-email/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/tas-integration/pom.xml b/packaging/tests/tas-integration/pom.xml index b911d2d82f..ffd578b6d8 100644 --- a/packaging/tests/tas-integration/pom.xml +++ b/packaging/tests/tas-integration/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/tas-restapi/pom.xml b/packaging/tests/tas-restapi/pom.xml index 01690096c8..aa14179787 100644 --- a/packaging/tests/tas-restapi/pom.xml +++ b/packaging/tests/tas-restapi/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/tests/tas-webdav/pom.xml b/packaging/tests/tas-webdav/pom.xml index 51a993a75b..b10f8ed8d9 100644 --- a/packaging/tests/tas-webdav/pom.xml +++ b/packaging/tests/tas-webdav/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-community-repo-tests - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/packaging/war/pom.xml b/packaging/war/pom.xml index 4ee6befcfb..d4391f5b38 100644 --- a/packaging/war/pom.xml +++ b/packaging/war/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo-packaging - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/pom.xml b/pom.xml index 6d99d12e3b..7f2ab284b9 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT pom Alfresco Community Repo Parent @@ -85,7 +85,7 @@ 3.4.2.Final 3.7.4 5.16.1 - 1.20 + 1.21 1.2.5 4.1.0 3.38.0 @@ -112,7 +112,7 @@ 8.0.25 8 2.7.2 - 3.0.44 + 3.0.45 3.3.0 1.61 1.30 diff --git a/remote-api/pom.xml b/remote-api/pom.xml index 6d93e5af4c..94315056be 100644 --- a/remote-api/pom.xml +++ b/remote-api/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java index a655e2b631..dec5f19b01 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java @@ -29,6 +29,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.function.Supplier; import org.springframework.extensions.surf.util.Content; import org.springframework.extensions.webscripts.Description.FormatStyle; @@ -38,15 +39,15 @@ import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WrappingWebScriptRequest; import org.springframework.util.FileCopyUtils; -public class BufferedRequest implements WrappingWebScriptRequest +public class BufferedRequest implements WrappingWebScriptRequest, AutoCloseable { - private TempOutputStreamFactory streamFactory; - private WebScriptRequest req; + private final Supplier streamFactory; + private final WebScriptRequest req; private TempOutputStream bufferStream; private InputStream contentStream; private BufferedReader contentReader; - public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory) + public BufferedRequest(WebScriptRequest req, Supplier streamFactory) { this.req = req; this.streamFactory = streamFactory; @@ -56,7 +57,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { if (bufferStream == null) { - bufferStream = streamFactory.createOutputStream(); + bufferStream = streamFactory.get(); try { @@ -81,7 +82,7 @@ public class BufferedRequest implements WrappingWebScriptRequest } if (contentStream == null) { - contentStream = getBufferedBodyAsTempStream().getInputStream(); + contentStream = getBufferedBodyAsTempStream().toNewInputStream(); } return contentStream; @@ -95,7 +96,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { contentStream.close(); } - catch (Exception e) + catch (Exception ignore) { } contentStream = null; @@ -106,13 +107,14 @@ public class BufferedRequest implements WrappingWebScriptRequest { contentReader.close(); } - catch (Exception e) + catch (Exception ignore) { } contentReader = null; } } - + + @Override public void close() { reset(); @@ -122,7 +124,7 @@ public class BufferedRequest implements WrappingWebScriptRequest { bufferStream.destroy(); } - catch (Exception e) + catch (Exception ignore) { } bufferStream = null; diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java index f5811b01ad..4e082052dd 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java @@ -28,6 +28,7 @@ package org.alfresco.repo.web.scripts; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; +import java.util.function.Supplier; import org.alfresco.error.AlfrescoRuntimeException; import org.apache.commons.logging.Log; @@ -42,25 +43,24 @@ import org.springframework.util.FileCopyUtils; /** * Transactional Buffered Response */ -public class BufferedResponse implements WrappingWebScriptResponse +public class BufferedResponse implements WrappingWebScriptResponse, AutoCloseable { // Logger protected static final Log logger = LogFactory.getLog(BufferedResponse.class); - private TempOutputStreamFactory streamFactory; - private WebScriptResponse res; - private int bufferSize; - private TempOutputStream outputStream = null; - private StringBuilderWriter outputWriter = null; - + private final Supplier streamFactory; + private final WebScriptResponse res; + private final int bufferSize; + private TempOutputStream outputStream; + private StringBuilderWriter outputWriter; /** * Construct - * - * @param res WebScriptResponse + * + * @param res WebScriptResponse * @param bufferSize int */ - public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory) + public BufferedResponse(WebScriptResponse res, int bufferSize, Supplier streamFactory) { this.res = res; this.bufferSize = bufferSize; @@ -71,6 +71,7 @@ public class BufferedResponse implements WrappingWebScriptResponse * (non-Javadoc) * @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext() */ + @Override public WebScriptResponse getNext() { return res; @@ -123,16 +124,18 @@ public class BufferedResponse implements WrappingWebScriptResponse * (non-Javadoc) * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() */ + @Override public OutputStream getOutputStream() throws IOException { - if (outputStream == null) + if (outputStream != null) { - if (outputWriter != null) - { - throw new AlfrescoRuntimeException("Already buffering output writer"); - } - outputStream = streamFactory.createOutputStream(); + return outputStream; } + if (outputWriter != null) + { + throw new AlfrescoRuntimeException("Already buffering output writer"); + } + outputStream = streamFactory.get(); return outputStream; } @@ -151,14 +154,15 @@ public class BufferedResponse implements WrappingWebScriptResponse */ public Writer getWriter() throws IOException { - if (outputWriter == null) + if (outputWriter != null) { - if (outputStream != null) - { - throw new AlfrescoRuntimeException("Already buffering output stream"); - } - outputWriter = new StringBuilderWriter(bufferSize); + return outputWriter; } + if (outputStream != null) + { + throw new AlfrescoRuntimeException("Already buffering output stream"); + } + outputWriter = new StringBuilderWriter(bufferSize); return outputWriter; } @@ -262,15 +266,7 @@ public class BufferedResponse implements WrappingWebScriptResponse if (logger.isDebugEnabled()) logger.debug("Writing Transactional response: size=" + outputStream.getLength()); - try - { - outputStream.flush(); - FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream()); - } - finally - { - outputStream.destroy(); - } + FileCopyUtils.copy(outputStream.toNewInputStream(), res.getOutputStream()); } } catch (IOException e) @@ -278,4 +274,20 @@ public class BufferedResponse implements WrappingWebScriptResponse throw new AlfrescoRuntimeException("Failed to commit buffered response", e); } } + + @Override + public void close() + { + if (outputStream != null) + { + try + { + outputStream.destroy(); + } + catch (Exception ignore) + { + } + outputStream = null; + } + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java index 7e1728be5c..bbc5f018a9 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java @@ -25,12 +25,12 @@ */ package org.alfresco.repo.web.scripts; -import java.io.File; import java.io.IOException; import java.net.SocketException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import javax.servlet.http.HttpServletResponse; import javax.transaction.Status; @@ -40,7 +40,6 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.ExceptionStackUtil; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -95,8 +94,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer private String tempDirectoryName = null; private int memoryThreshold = 4 * 1024 * 1024; // 4mb private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb - private TempOutputStreamFactory streamFactory = null; - private TempOutputStreamFactory responseStreamFactory = null; + private Supplier streamFactory = null; private String preserveHeadersPattern = null; private Class[] notPublicExceptions = new Class[] {}; @@ -107,17 +105,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer */ public void setup() { - File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); - this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false); - this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, - encryptTempFiles, false); + streamFactory = TempOutputStream.factory( + TempFileProvider.getTempDir(tempDirectoryName), + memoryThreshold, maxContentSize, encryptTempFiles); } public void setEncryptTempFiles(Boolean encryptTempFiles) { if(encryptTempFiles != null) { - this.encryptTempFiles = encryptTempFiles.booleanValue(); + this.encryptTempFiles = encryptTempFiles; } } @@ -130,7 +127,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer { if(memoryThreshold != null) { - this.memoryThreshold = memoryThreshold.intValue(); + this.memoryThreshold = memoryThreshold; } } @@ -138,7 +135,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer { if(maxContentSize != null) { - this.maxContentSize = maxContentSize.longValue(); + this.maxContentSize = maxContentSize; } } @@ -246,8 +243,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer */ public Map getScriptParameters() { - Map params = new HashMap(); - params.putAll(super.getScriptParameters()); + Map params = new HashMap<>(super.getScriptParameters()); addRepoParameters(params); return params; } @@ -259,16 +255,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer public Map getTemplateParameters() { // Ensure we have a transaction - we might be generating the status template after the main transaction failed - return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback>() - { - public Map execute() throws Throwable - { - Map params = new HashMap(); - params.putAll(RepositoryContainer.super.getTemplateParameters()); - params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver()); - addRepoParameters(params); - return params; - } + return fallbackTransactionHelper.doInTransaction(() -> { + Map params = new HashMap<>(RepositoryContainer.super.getTemplateParameters()); + params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver()); + addRepoParameters(params); + return params; }, true); } @@ -321,7 +312,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions); if (displayCause == null && hideCause != null) { - AlfrescoRuntimeException alf = null; + final AlfrescoRuntimeException alf; if (e instanceof AlfrescoRuntimeException) { alf = (AlfrescoRuntimeException) e; @@ -342,116 +333,110 @@ public class RepositoryContainer extends AbstractRuntimeContainer } } - protected void executeScriptInternal(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth) + protected void executeScriptInternal(final WebScriptRequest scriptReq, final WebScriptResponse scriptRes, final Authenticator auth) throws IOException { final WebScript script = scriptReq.getServiceMatch().getWebScript(); final Description desc = script.getDescription(); final boolean debug = logger.isDebugEnabled(); - + // Escalate the webscript declared level of authentication to the container required authentication // eg. must be guest if MT is enabled unless credentials are empty - RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); + final RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); final RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication()); final boolean isGuest = scriptReq.isGuest(); - + if (required == RequiredAuthentication.none) { // TODO revisit - cleared here, in-lieu of WebClient clear //AuthenticationUtil.clearCurrentSecurityContext(); - + transactionedExecuteAs(script, scriptReq, scriptRes); + return; } - else if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest) + + if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest) { throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); } - else + + try { - try + AuthenticationUtil.pushAuthentication(); + + // + // Determine if user already authenticated + // + if (debug) { - AuthenticationUtil.pushAuthentication(); - - // - // Determine if user already authenticated - // - if (debug) - { - String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); - logger.debug("Authentication required: " + required); - logger.debug("Guest login requested: " + isGuest); - } - - // - // Apply appropriate authentication to Web Script invocation - // - RetryingTransactionCallback authWork = new RetryingTransactionCallback() - { - public Boolean execute() throws Exception - { - if (auth == null || auth.authenticate(required, isGuest)) - { - // The user will now have been authenticated, based on HTTP Auth, Ticket etc - // Check that the user they authenticated as has appropriate access to the script - - // Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more - if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin) - { - String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); - String runAsUser = AuthenticationUtil.getRunAsUser(); - - if ( (authenticatedUser == null) || - (authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) || - (!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) ) - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); - } - } - - // Check to see if they're admin or system on an Admin only script - if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName()))) - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access."); - } - - if (debug) - { - String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); - } - - return true; - } - return false; - } - }; - - boolean readOnly = transactionService.isReadOnly(); - boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; - if (transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew)) - { - // Execute Web Script if authentication passed - // The Web Script has its own txn management with potential runAs() user - transactionedExecuteAs(script, scriptReq, scriptRes); - } - else - { - throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId()); - } + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + logger.debug("Authentication required: " + required); + logger.debug("Guest login requested: " + isGuest); } - finally - { - // - // Reset authentication for current thread - // - AuthenticationUtil.popAuthentication(); - + + // + // Apply appropriate authentication to Web Script invocation + // + final RetryingTransactionCallback authWork = () -> { + if (auth != null && !auth.authenticate(required, isGuest)) + { + return false; + } + // The user will now have been authenticated, based on HTTP Auth, Ticket etc + // Check that the user they authenticated as has appropriate access to the script + + // Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more + if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin) + { + final String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final String runAsUser = AuthenticationUtil.getRunAsUser(); + + if ( (authenticatedUser == null) || + (authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) || + (!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) ) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); + } + } + + // Check to see if they're admin or system on an Admin only script + if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName()))) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access."); + } + if (debug) { String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); } + + return true; + }; + + final boolean readOnly = transactionService.isReadOnly(); + final boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; + if (!transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew)) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId()); + } + + // Execute Web Script if authentication passed + // The Web Script has its own txn management with potential runAs() user + transactionedExecuteAs(script, scriptReq, scriptRes); + } + finally + { + // + // Reset authentication for current thread + // + AuthenticationUtil.popAuthentication(); + + if (debug) + { + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); } } } @@ -467,191 +452,160 @@ public class RepositoryContainer extends AbstractRuntimeContainer protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) throws IOException { + final Description description = script.getDescription(); + try { - final Description description = script.getDescription(); if (description.getRequiredTransaction() == RequiredTransaction.none) { script.execute(scriptReq, scriptRes); + return; } - else + } + catch (IOException e) + { + handleIOException(e); + } + + final RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); + + try (final BufferedRequest bufferedReq = newBufferedRequest(trxParams, scriptReq, streamFactory); + final BufferedResponse bufferedRes = newBufferedResponse(trxParams, scriptRes, streamFactory)) + { + boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly; + boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew; + + // log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should + // NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179. + if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase( + description.getMethod())) { - final BufferedRequest bufferedReq; - final BufferedResponse bufferedRes; - RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); - if (trxParams.getCapability() == TransactionCapability.readwrite) - { - if (trxParams.getBufferSize() > 0) - { - if (logger.isDebugEnabled()) - logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); + logger.debug("Webscript with URL '" + scriptReq.getURL() + + "' is a GET request but it's descriptor has declared a readwrite transaction is required"); + } - // create buffered request and response that allow transaction retrying - bufferedReq = new BufferedRequest(scriptReq, streamFactory); - bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory); - } - else + try + { + final RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper(); + if (script instanceof LoginPost) + { + //login script requires read-write transaction because of authorization interceptor + transactionHelper.setForceWritable(true); + } + transactionHelper.doInTransaction(() -> { + try { if (logger.isDebugEnabled()) - logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0"); - bufferedReq = null; - bufferedRes = null; - } - } - else - { - bufferedReq = null; - bufferedRes = null; - } - - // encapsulate script within transaction - RetryingTransactionCallback work = new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - try + logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," + + description.getRequiredTransactionParameters().getCapability()); + + if (bufferedReq == null || bufferedRes == null) { - if (logger.isDebugEnabled()) - logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + "," - + description.getRequiredTransactionParameters().getCapability()); - - if (bufferedRes == null) - { - script.execute(scriptReq, scriptRes); - } - else - { - // Reset the request and response in case of a transaction retry - bufferedReq.reset(); - // REPO-4388 don't reset specified headers - bufferedRes.reset(preserveHeadersPattern); - script.execute(bufferedReq, bufferedRes); - } + script.execute(scriptReq, scriptRes); } - catch(Exception e) + else { - if (logger.isDebugEnabled()) - { - logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); - // Note: user transaction shouldn't be null, but just in case inside this exception handler - UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); - if (userTrx != null) - { - logger.debug("Transaction status: " + userTrx.getStatus()); - } - } - + // Reset the request and response in case of a transaction retry + bufferedReq.reset(); + // REPO-4388 don't reset specified headers + bufferedRes.reset(preserveHeadersPattern); + script.execute(bufferedReq, bufferedRes); + } + } + catch (Exception e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); + // Note: user transaction shouldn't be null, but just in case inside this exception handler UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); if (userTrx != null) { - if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK) + logger.debug("Transaction status: " + userTrx.getStatus()); + } + } + + final UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); + if (userTrx != null) + { + if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK) + { + if (logger.isDebugEnabled()) + logger.debug("Marking web script transaction for rollback"); + try + { + userTrx.setRollbackOnly(); + } + catch (Throwable re) { if (logger.isDebugEnabled()) - logger.debug("Marking web script transaction for rollback"); - try - { - userTrx.setRollbackOnly(); - } - catch(Throwable re) - { - if (logger.isDebugEnabled()) - logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage()); - } + logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage()); } } - - // re-throw original exception for retry - throw e; } - finally - { - if (logger.isDebugEnabled()) - logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," - + description.getRequiredTransactionParameters().getCapability()); - } - - return null; - } - }; - - boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly; - boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew; - - // log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should - // NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179. - if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase(description.getMethod())) - { - logger.debug("Webscript with URL '" + scriptReq.getURL() + - "' is a GET request but it's descriptor has declared a readwrite transaction is required"); - } - - try - { - RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper(); - if(script instanceof LoginPost) - { - //login script requires read-write transaction because of authorization intercepter - transactionHelper.setForceWritable(true); - } - transactionHelper.doInTransaction(work, readonly, requiresNew); - } - catch (TooBusyException e) - { - // Map TooBusyException to a 503 status code - throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e); - } - finally - { - // Get rid of any temporary files - if (bufferedReq != null) - { - bufferedReq.close(); - } - } - // Ensure a response is always flushed after successful execution - if (bufferedRes != null) - { - bufferedRes.writeResponse(); - } - + // re-throw original exception for retry + throw e; + } + finally + { + if (logger.isDebugEnabled()) + logger.debug("End retry transaction block: " + description.getRequiredTransaction() + "," + + description.getRequiredTransactionParameters().getCapability()); + } + + return null; + }, readonly, requiresNew); } - } - catch (IOException ioe) - { - Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); - Class clientAbortException = null; - try + catch (TooBusyException e) { - clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); + // Map TooBusyException to a 503 status code + throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e); } - catch (ClassNotFoundException e) + + // Ensure a response is always flushed after successful execution + if (bufferedRes != null) { - // do nothing - } - // Note: if you need to look for more exceptions in the stack, then create a static array and pass it in - if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null)) - { - if (logger.isDebugEnabled()) - { - logger.warn("Client has cut off communication", ioe); - } - else - { - logger.info("Client has cut off communication"); - } - } - else - { - throw ioe; + bufferedRes.writeResponse(); } } } - + + private static void handleIOException(final IOException ioe) throws IOException + { + Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); + Class clientAbortException = null; + try + { + clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); + } + catch (ClassNotFoundException e) + { + // do nothing + } + // Note: if you need to look for more exceptions in the stack, then create a static array and pass it in + if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || + (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null)) + { + if (logger.isDebugEnabled()) + { + logger.warn("Client has cut off communication", ioe); + } + else + { + logger.info("Client has cut off communication"); + } + } + else + { + throw ioe; + } + } + /** * Execute script within required level of transaction as required effective user. - * - * @param script WebScript + * + * @param script WebScript * @param scriptReq WebScriptRequest * @param scriptRes WebScriptResponse * @throws IOException @@ -659,22 +613,17 @@ public class RepositoryContainer extends AbstractRuntimeContainer private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) throws IOException { - String runAs = script.getDescription().getRunAs(); + final String runAs = script.getDescription().getRunAs(); if (runAs == null) { transactionedExecute(script, scriptReq, scriptRes); } else { - RunAsWork work = new RunAsWork() - { - public Object doWork() throws Exception - { - transactionedExecute(script, scriptReq, scriptRes); - return null; - } - }; - AuthenticationUtil.runAs(work, runAs); + AuthenticationUtil.runAs(() -> { + transactionedExecute(script, scriptReq, scriptRes); + return null; + }, runAs); } } @@ -688,17 +637,12 @@ public class RepositoryContainer extends AbstractRuntimeContainer { ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event; ApplicationContext refreshContext = refreshEvent.getApplicationContext(); - if (refreshContext != null && refreshContext.equals(applicationContext)) + if (refreshContext.equals(applicationContext)) { - RunAsWork work = new RunAsWork() - { - public Object doWork() throws Exception - { - reset(); - return null; - } - }; - AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName()); + AuthenticationUtil.runAs(() -> { + reset(); + return null; + }, AuthenticationUtil.getSystemUserName()); } } } @@ -739,18 +683,54 @@ public class RepositoryContainer extends AbstractRuntimeContainer @Override public void reset() { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Exception - { - internalReset(); - return null; - } + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + internalReset(); + return null; }, true, false); } - + private void internalReset() { super.reset(); } + + private static BufferedRequest newBufferedRequest( + final RequiredTransactionParameters trxParams, + final WebScriptRequest scriptReq, + final Supplier streamFactory) + { + if (trxParams.getCapability() != TransactionCapability.readwrite) + { + return null; + } + if (trxParams.getBufferSize() <= 0) + { + return null; + } + + // create buffered request that allow transaction retrying + return new BufferedRequest(scriptReq, streamFactory); + } + + private static BufferedResponse newBufferedResponse( + final RequiredTransactionParameters trxParams, + final WebScriptResponse scriptRes, + final Supplier streamFactory) + { + if (trxParams.getCapability() != TransactionCapability.readwrite) + { + return null; + } + if (trxParams.getBufferSize() <= 0) + { + if (logger.isDebugEnabled()) + logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0"); + return null; + } + if (logger.isDebugEnabled()) + logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); + + // create buffered response that allow transaction retrying + return new BufferedResponse(scriptRes, trxParams.getBufferSize(), streamFactory); + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java index e9f6df704e..8ebef82486 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.Key; +import java.util.function.Supplier; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -88,13 +89,11 @@ public class TempOutputStream extends OutputStream private final File tempDir; private final int memoryThreshold; private final long maxContentSize; - private boolean encrypt; - private boolean deleteTempFileOnClose; + private final boolean encrypt; private long length = 0; private OutputStream outputStream; private File tempFile; - private TempByteArrayOutputStream tempStream; private Key symKey; private byte[] iv; @@ -112,58 +111,49 @@ public class TempOutputStream extends OutputStream * the max content size in B * @param encrypt * true if temp files should be encrypted - * @param deleteTempFileOnClose - * true if temp files should be deleted on output stream close - * (useful if we need to cache the content for further reads). If - * this is false then we need to make sure we call - * {@link TempOutputStream}.destroy to clean up properly. */ - public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose) + public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt) { this.tempDir = tempDir; this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold; this.maxContentSize = maxContentSize; this.encrypt = encrypt; - this.deleteTempFileOnClose = deleteTempFileOnClose; - this.tempStream = new TempByteArrayOutputStream(); - this.outputStream = this.tempStream; + this.outputStream = new ByteArrayOutputStream(); } /** * Returns the data as an InputStream */ - public InputStream getInputStream() throws IOException + public InputStream toNewInputStream() throws IOException { - if (tempFile != null) + closeOutputStream(); + + if (tempFile == null) + { + return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray()); + } + if (!encrypt) { - if (encrypt) - { - final Cipher cipher; - try - { - cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv)); - } - catch (Exception e) - { - destroy(); - - if (logger.isErrorEnabled()) - { - logger.error("Cannot initialize decryption cipher", e); - } - - throw new IOException("Cannot initialize decryption cipher", e); - } - - return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher)); - } return new BufferedInputStream(new FileInputStream(tempFile)); } - else + try { - return new ByteArrayInputStream(tempStream.getBuffer(), 0, tempStream.getCount()); + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv)); + + return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher)); + } + catch (Exception e) + { + destroy(); + + if (logger.isErrorEnabled()) + { + logger.error("Cannot initialize decryption cipher", e); + } + + throw new IOException("Cannot initialize decryption cipher", e); } } @@ -190,7 +180,7 @@ public class TempOutputStream extends OutputStream @Override public void close() throws IOException { - close(deleteTempFileOnClose); + closeOutputStream(); } /** @@ -215,7 +205,9 @@ public class TempOutputStream extends OutputStream */ public void destroy() throws IOException { - close(true); + closeOutputStream(); + + deleteTempFile(); } public long getLength() @@ -282,102 +274,95 @@ public class TempOutputStream extends OutputStream } } - private void close(boolean deleteTempFileOnClose) + private BufferedOutputStream createFileOutputStream(final File file) throws IOException { - closeOutputStream(); - - if (deleteTempFileOnClose) + if (!encrypt) { - deleteTempFile(); + return new BufferedOutputStream(new FileOutputStream(file)); } - } - - private BufferedOutputStream createOutputStream(File file) throws IOException - { - BufferedOutputStream fileOutputStream; - if (encrypt) + try { - try + // Generate a symmetric key + final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM); + keyGen.init(KEY_SIZE); + symKey = keyGen.generateKey(); + + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, symKey); + + iv = cipher.getIV(); + + return new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher)); + } + catch (Exception e) + { + if (logger.isErrorEnabled()) { - // Generate a symmetric key - final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM); - keyGen.init(KEY_SIZE); - symKey = keyGen.generateKey(); - - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, symKey); - - iv = cipher.getIV(); - - fileOutputStream = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher)); + logger.error("Cannot initialize encryption cipher", e); } - catch (Exception e) - { - if (logger.isErrorEnabled()) - { - logger.error("Cannot initialize encryption cipher", e); - } - throw new IOException("Cannot initialize encryption cipher", e); - } + throw new IOException("Cannot initialize encryption cipher", e); } - else - { - fileOutputStream = new BufferedOutputStream(new FileOutputStream(file)); - } - - return fileOutputStream; } private void update(int len) throws IOException { - if (maxContentSize > -1 && length + len > maxContentSize) + if (surpassesMaxContentSize(len)) { destroy(); throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize); } - if (tempFile == null && (tempStream.getCount() + len) > memoryThreshold) + if (surpassesThreshold(len)) { - File file = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir); + tempFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir); - BufferedOutputStream fileOutputStream = createOutputStream(file); - fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount()); + final BufferedOutputStream fileOutputStream = createFileOutputStream(tempFile); + fileOutputStream.write(((ByteArrayOutputStream) outputStream).toByteArray()); fileOutputStream.flush(); try { - tempStream.close(); + outputStream.close(); } - catch (IOException e) + catch (IOException ignore) { // Ignore exception } - tempStream = null; - tempFile = file; outputStream = fileOutputStream; } length += len; } - private static class TempByteArrayOutputStream extends ByteArrayOutputStream + private boolean surpassesMaxContentSize(final int len) { - /** - * @return The internal buffer where data is stored - */ - public byte[] getBuffer() - { - return buf; - } + return maxContentSize >= 0 && length + len > maxContentSize; + } - /** - * @return The number of valid bytes in the buffer. - */ - public int getCount() - { - return count; - } + private boolean surpassesThreshold(final int len) + { + return tempFile == null && length + len > memoryThreshold; + } + + /** + * Creates a {@link TempOutputStream} factory/supplier. + * + * @param tempDir + * the temporary directory, i.e. isDir == true, that + * will be used as * parent directory for creating temp file backed + * streams + * @param memoryThreshold + * the memory threshold in B + * @param maxContentSize + * the max content size in B + * @param encrypt + * true if temp files should be encrypted + */ + public static Supplier factory(final File tempDir, final int memoryThreshold, + final long maxContentSize, final boolean encrypt) + { + return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt); } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java deleted file mode 100644 index 2300bb8b03..0000000000 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ -package org.alfresco.repo.web.scripts; - -import java.io.File; - -/** - * Factory for {@link TempOutputStream} - */ -public class TempOutputStreamFactory -{ - /** - * A temporary directory, i.e. isDir == true, that will be used as - * parent directory for creating temp file backed streams. - */ - private final File tempDir; - private int memoryThreshold; - private long maxContentSize; - private boolean encrypt; - private boolean deleteTempFileOnClose; - - /** - * Creates a {@link TempOutputStream} factory. - * - * @param tempDir - * the temporary directory, i.e. isDir == true, that - * will be used as * parent directory for creating temp file backed - * streams - * @param memoryThreshold - * the memory threshold in B - * @param maxContentSize - * the max content size in B - * @param encrypt - * true if temp files should be encrypted - * @param deleteTempFileOnClose - * true if temp files should be deleted on output stream close - * (useful if we need to cache the content for further reads). If - * this is false then we need to make sure we call - * {@link TempOutputStream}.destroy to clean up properly. - */ - public TempOutputStreamFactory(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose) - { - this.tempDir = tempDir; - this.memoryThreshold = memoryThreshold; - this.maxContentSize = maxContentSize; - this.encrypt = encrypt; - this.deleteTempFileOnClose = deleteTempFileOnClose; - } - - /** - * Creates a new {@link TempOutputStream} object - */ - public TempOutputStream createOutputStream() - { - return new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt, deleteTempFileOnClose); - } - - public File getTempDir() - { - return tempDir; - } - - public int getMemoryThreshold() - { - return memoryThreshold; - } - - public long getMaxContentSize() - { - return maxContentSize; - } - - public boolean isEncrypt() - { - return encrypt; - } - - public boolean isDeleteTempFileOnClose() - { - return deleteTempFileOnClose; - } -} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java index 188353b4c2..afd1e1278e 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java @@ -23,149 +23,144 @@ * along with Alfresco. If not, see . * #L% */ -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(); - } - } -} +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 + try (OutputStream os = res.getOutputStream()) + { + 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); + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java index afe7a29b94..4b756df5d3 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java @@ -125,16 +125,15 @@ public class PostSnapshotCommandProcessor implements CommandProcessor logger.debug("success"); resp.setStatus(Status.STATUS_OK); - - OutputStream out = resp.getOutputStream(); - resp.setContentType("text/xml"); - resp.setContentEncoding("utf-8"); - - receiver.generateRequsite(transferId, out); - - out.close(); - - } + + try (OutputStream out = resp.getOutputStream()) + { + resp.setContentType("text/xml"); + resp.setContentEncoding("utf-8"); + + receiver.generateRequsite(transferId, out); + } + } catch (Exception ex) { logger.debug("exception caught", ex); diff --git a/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java new file mode 100644 index 0000000000..93d49d3c93 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java @@ -0,0 +1,66 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api; + +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; +import org.apache.commons.lang3.BooleanUtils; + +/** + * Helper class for retrieving direct access URLs options. + * + * @author Sara Aspery + */ +public class DirectAccessUrlHelper +{ + private RestApiDirectUrlConfig restApiDirectUrlConfig; + + public void setRestApiDirectUrlConfig(RestApiDirectUrlConfig restApiDirectUrlConfig) + { + this.restApiDirectUrlConfig = restApiDirectUrlConfig; + } + + public Long getDefaultExpiryTimeInSec() + { + if (restApiDirectUrlConfig ==null || !restApiDirectUrlConfig.isEnabled()) + { + throw new DisabledServiceException("Direct access url isn't available."); + } + + return restApiDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + + public boolean getAttachment(DirectAccessUrlRequest directAccessUrlRequest) + { + boolean attachment = true; + if (directAccessUrlRequest != null ) + { + attachment = BooleanUtils.toBooleanDefaultIfNull(directAccessUrlRequest.isAttachment(), true); + } + return attachment; + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java index 48f1332d83..6fa9272e3d 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -44,6 +44,7 @@ import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; @@ -266,6 +267,49 @@ public interface Nodes */ Node unlock(String nodeId, Parameters parameters); + /** + * Gets a presigned URL to directly access content. + * @param nodeId The node id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, boolean attachment) + { + return requestContentDirectUrl(validateNode(nodeId), attachment); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef The node reference for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment) + { + return requestContentDirectUrl(nodeRef, attachment, null); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeId The node id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String nodeId, boolean attachment, Long validFor) + { + return requestContentDirectUrl(validateNode(nodeId), attachment, validFor); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef The node reference for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); + /** * Convert from node properties (map of QName to Serializable) retrieved from * the respository to a map of String to Object that can be formatted/expressed diff --git a/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java b/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java index c163447fd0..b4ee326711 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscript.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,6 +25,7 @@ */ package org.alfresco.rest.api.discovery; +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; import org.alfresco.rest.api.model.DiscoveryDetails; import org.alfresco.rest.api.model.ModulePackage; import org.alfresco.rest.api.model.RepositoryInfo; @@ -41,6 +42,7 @@ import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.module.ModuleDetails; import org.alfresco.service.cmr.module.ModuleService; import org.alfresco.service.cmr.quickshare.QuickShareService; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; @@ -67,6 +69,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz private ModuleService moduleService; private ApiAssistant assistant; private ThumbnailService thumbnailService; + private RestApiDirectUrlConfig restApiDirectUrlConfig; + private ContentService contentService; private boolean enabled = true; private final static String DISABLED = "Not Implemented"; @@ -106,6 +110,16 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz this.thumbnailService = thumbnailService; } + public void setRestApiDirectUrlConfig(RestApiDirectUrlConfig restApiDirectUrlConfig) + { + this.restApiDirectUrlConfig = restApiDirectUrlConfig; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + @Override public void afterPropertiesSet() throws Exception { @@ -116,6 +130,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz PropertyCheck.mandatory(this, "moduleService", moduleService); PropertyCheck.mandatory(this, "assistant", assistant); PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); + PropertyCheck.mandatory(this, "restApiDirectUrlConfig", restApiDirectUrlConfig); + PropertyCheck.mandatory(this, "contentService", contentService); } @Override @@ -154,7 +170,8 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz .setReadOnly(repoAdminService.getUsage().isReadOnly()) .setAuditEnabled(auditService.isAuditEnabled()) .setQuickShareEnabled(quickShareService.isQuickShareEnabled()) - .setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled())); + .setThumbnailGenerationEnabled(thumbnailService.getThumbnailsEnabled()) + .setDirectAccessUrlEnabled(isContentDirectUrlEnabled())); } private List getModules() @@ -194,4 +211,10 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz throw new DisabledServiceException(DISABLED); } } + + protected boolean isContentDirectUrlEnabled() + { + return (restApiDirectUrlConfig.isEnabled() && contentService.isContentDirectUrlEnabled()); + } + } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java index ed8bb25a44..1f88b43ca7 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -139,6 +139,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; @@ -3413,6 +3414,20 @@ public class NodesImpl implements Nodes return getFolderOrDocument(nodeId, parameters); } + /** + * {@inheritDoc} + */ + @Override + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(nodeRef, attachment, validFor); + if (directAccessUrl == null) + { + throw new DisabledServiceException("Direct access url isn't available."); + } + return directAccessUrl; + } + /** * Checks if same permission is sent more than once * @param locallySetPermissions diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java new file mode 100644 index 0000000000..3b231ae7e1 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/directurl/RestApiDirectUrlConfig.java @@ -0,0 +1,93 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api.impl.directurl; + +import org.alfresco.repo.content.directurl.AbstractDirectUrlConfig; +import org.alfresco.repo.content.directurl.InvalidDirectAccessUrlConfigException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * REST API direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class RestApiDirectUrlConfig extends AbstractDirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(RestApiDirectUrlConfig.class); + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs for the REST API if any error found in the REST API direct access URL config + try + { + validateDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling REST API direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + } + + /* Helper method to validate the REST API direct access url configuration settings */ + private void validateDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getDefaultExpiryTimeInSec() == null) + { + logger.warn(String.format("Default expiry time property is missing: setting to system-wide default [%s].", getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + + if (getDefaultExpiryTimeInSec() < 1) + { + String errorMsg = String.format("REST API direct access URL default expiry time [%s] is invalid.", getDefaultExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("REST API direct access URL default expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java new file mode 100644 index 0000000000..6dde68adb2 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java @@ -0,0 +1,46 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api.model; + +/** + * Direct Access URL request. + * + * @author Sara Aspery + */ +public class DirectAccessUrlRequest +{ + private Boolean attachment; + + public Boolean isAttachment() + { + return attachment; + } + + public void setAttachment(Boolean attachment) + { + this.attachment = attachment; + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java b/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java index f65aa8c5c7..60bbadd32a 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/RepositoryInfo.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -416,6 +416,7 @@ public class RepositoryInfo private boolean isAuditEnabled; private boolean isQuickShareEnabled; private boolean isThumbnailGenerationEnabled; + private boolean isDirectAccessUrlEnabled; public StatusInfo() { @@ -465,6 +466,17 @@ public class RepositoryInfo return this; } + public boolean getIsDirectAccessUrlEnabled() + { + return isDirectAccessUrlEnabled; + } + + public StatusInfo setDirectAccessUrlEnabled(boolean isDirectAccessUrlEnabled) + { + this.isDirectAccessUrlEnabled = isDirectAccessUrlEnabled; + return this; + } + @Override public String toString() { @@ -473,6 +485,7 @@ public class RepositoryInfo .append(", isAuditEnabled=").append(isAuditEnabled) .append(", isQuickShareEnabled=").append(isQuickShareEnabled) .append(", isThumbnailGenerationEnabled=").append(isThumbnailGenerationEnabled) + .append(", isDirectAccessUrlEnabled=").append(isDirectAccessUrlEnabled) .append(']'); return sb.toString(); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionsRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionsRelation.java index 6b74e4293f..b4bfc20417 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionsRelation.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionsRelation.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -26,16 +26,22 @@ package org.alfresco.rest.api.nodes; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; import org.alfresco.repo.node.integrity.IntegrityException; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.VersionModel; +import org.alfresco.rest.api.DirectAccessUrlHelper; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.api.model.VersionOptions; import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.ResourceParameter; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.resource.RelationshipResource; @@ -46,6 +52,7 @@ import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.webscripts.WithResponse; import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; @@ -80,6 +87,12 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements { protected VersionService versionService; protected BehaviourFilter behaviourFilter; + private DirectAccessUrlHelper directAccessUrlHelper; + + public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper) + { + this.directAccessUrlHelper = directAccessUrlHelper; + } @Override public void afterPropertiesSet() @@ -288,4 +301,32 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements } return null; } + + @Operation("requestNodeDirectAccessUrl") + @WebApiParam (name = "requestNodeDirectAccessUrl", title = "Request direct access url", description = "Request direct access url", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT) + @WebApiDescription(title = "Request content url", + description="Generates a direct access URL.", + successStatus = HttpServletResponse.SC_OK) + public DirectAccessUrl requestContentDirectUrl(String nodeId, String versionId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse) + { + boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest); + Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec(); + Version version = findVersion(nodeId, versionId); + if (version != null) + { + NodeRef versionNodeRef = version.getFrozenStateNodeRef(); + + DirectAccessUrl directAccessUrl; + try + { + directAccessUrl = nodes.requestContentDirectUrl(versionNodeRef, attachment, validFor); + } + catch (DirectAccessUrlDisabledException ex) + { + throw new DisabledServiceException(ex.getMessage()); + } + return directAccessUrl; + } + throw new EntityNotFoundException(nodeId+"-"+versionId); + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodesEntityResource.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodesEntityResource.java index 7d7777aa31..c7cf7bca95 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodesEntityResource.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodesEntityResource.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,11 +25,13 @@ */ package org.alfresco.rest.api.nodes; +import javax.servlet.http.HttpServletResponse; import java.io.InputStream; -import javax.servlet.http.HttpServletResponse; - +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.rest.api.DirectAccessUrlHelper; import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.model.DirectAccessUrlRequest; import org.alfresco.rest.api.model.LockInfo; import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.NodeTarget; @@ -37,6 +39,8 @@ import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.Operation; import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.ResourceParameter; +import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.resource.EntityResource; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; @@ -45,7 +49,10 @@ import org.alfresco.rest.framework.resource.content.BasicContentInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.ParameterCheck; + import org.springframework.beans.factory.InitializingBean; /** @@ -61,13 +68,19 @@ public class NodesEntityResource implements BinaryResourceAction.Read, BinaryResourceAction.Update, InitializingBean { private Nodes nodes; + private DirectAccessUrlHelper directAccessUrlHelper; public void setNodes(Nodes nodes) { this.nodes = nodes; } - @Override + public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper) + { + this.directAccessUrlHelper = directAccessUrlHelper; + } + + @Override public void afterPropertiesSet() { ParameterCheck.mandatory("nodes", this.nodes); @@ -189,5 +202,27 @@ public class NodesEntityResource implements return nodes.unlock(nodeId, parameters); } + @Operation("requestNodeDirectAccessUrl") + @WebApiParam(name = "requestNodeDirectAccessUrl", title = "Request direct access url", description = "Request direct access url", kind = ResourceParameter.KIND.HTTP_BODY_OBJECT) + @WebApiDescription(title = "Request content url", + description="Generates a direct access URL.", + successStatus = HttpServletResponse.SC_OK) + public DirectAccessUrl requestContentDirectUrl(String nodeId, DirectAccessUrlRequest directAccessUrlRequest, Parameters parameters, WithResponse withResponse) + { + boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest); + Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec(); + NodeRef nodeRef = nodes.validateNode(nodeId); + + DirectAccessUrl directAccessUrl; + try + { + directAccessUrl = nodes.requestContentDirectUrl(nodeRef, attachment, validFor); + } + catch (DirectAccessUrlDisabledException ex) + { + throw new DisabledServiceException(ex.getMessage()); + } + return directAccessUrl; + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java index c848eb05e7..606cf3cf07 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java @@ -28,10 +28,11 @@ package org.alfresco.rest.framework.webscripts; import java.io.File; import java.io.IOException; import java.util.Map; +import java.util.function.Supplier; import org.alfresco.repo.web.scripts.BufferedRequest; import org.alfresco.repo.web.scripts.BufferedResponse; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; +import org.alfresco.repo.web.scripts.TempOutputStream; import org.alfresco.rest.framework.Api; import org.alfresco.rest.framework.tools.ApiAssistant; import org.alfresco.service.transaction.TransactionService; @@ -56,7 +57,7 @@ public abstract class ApiWebScript extends AbstractWebScript protected String tempDirectoryName = null; protected int memoryThreshold = 4 * 1024 * 1024; // 4mb protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb - protected TempOutputStreamFactory streamFactory = null; + protected Supplier streamFactory = null; protected TransactionService transactionService; public void setTransactionService(TransactionService transactionService) @@ -88,7 +89,7 @@ public abstract class ApiWebScript extends AbstractWebScript this.maxContentSize = maxContentSize; } - public void setStreamFactory(TempOutputStreamFactory streamFactory) + public void setStreamFactory(Supplier streamFactory) { this.streamFactory = streamFactory; } @@ -96,50 +97,38 @@ public abstract class ApiWebScript extends AbstractWebScript public void init() { File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); - this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, false); + streamFactory = TempOutputStream.factory(tempDirectory, memoryThreshold, maxContentSize, false); } @Override public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException { - Map templateVars = req.getServiceMatch().getTemplateVars(); - Api api = assistant.determineApi(templateVars); - - final BufferedRequest bufferedReq = getRequest(req); - final BufferedResponse bufferedRes = getResponse(res); + final Map templateVars = req.getServiceMatch().getTemplateVars(); + final Api api = ApiAssistant.determineApi(templateVars); - try - { - execute(api, bufferedReq, bufferedRes); - } - finally - { - // Get rid of any temporary files - if (bufferedReq != null) - { - bufferedReq.close(); - } - } - - // Ensure a response is always flushed after successful execution - if (bufferedRes != null) + try (final BufferedRequest bufferedReq = getRequest(req); + final BufferedResponse bufferedRes = getResponse(res)) { - bufferedRes.writeResponse(); + execute(api, bufferedReq, bufferedRes); + + // Ensure a response is always flushed after successful execution + if (bufferedRes != null) + { + bufferedRes.writeResponse(); + } } } protected BufferedRequest getRequest(final WebScriptRequest req) { // create buffered request and response that allow transaction retrying - final BufferedRequest bufferedReq = new BufferedRequest(req, streamFactory); - return bufferedReq; + return new BufferedRequest(req, streamFactory); } protected BufferedResponse getResponse(final WebScriptResponse resp) { // create buffered request and response that allow transaction retrying - final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory); - return bufferedRes; + return new BufferedResponse(resp, memoryThreshold, streamFactory); } public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException; diff --git a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java index ee2a7dbe2a..866d7cf704 100644 --- a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java +++ b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java @@ -23,110 +23,110 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.web.scripts; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.alfresco.repo.jscript.ScriptUtils; -import org.alfresco.repo.web.scripts.RepositoryContainer; -import org.alfresco.service.cmr.admin.RepoUsage; -import org.alfresco.service.cmr.repository.StoreRef; -import org.springframework.extensions.webscripts.WebScript; - -/** - * Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional - * Remote API methods using objects not available to base Repository project. - *

- * See "web-scripts-application-context.xml" for bean definition. - * - * @since 4.2.0 - * @since 5.1 (Moved to Remote API project) - * @author Kevin Roast - */ -public class WebScriptUtils extends ScriptUtils -{ - protected RepositoryContainer repositoryContainer; - - public void setRepositoryContainer(RepositoryContainer repositoryContainer) - { - this.repositoryContainer = repositoryContainer; - } - - /** - * Searches for webscript components with the given family name. - * - * @param family the family - * - * @return An array of webscripts that match the given family name - */ - public Object[] findWebScripts(String family) - { - List values = new ArrayList(); - - for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) - { - if (family != null) - { - Set familys = webscript.getDescription().getFamilys(); - if (familys != null && familys.contains(family)) - { - values.add(webscript.getDescription()); - } - } - else - { - values.add(webscript.getDescription()); - } - } - - return values.toArray(new Object[values.size()]); - } - - public String getHostAddress() - { - try - { - return InetAddress.getLocalHost().getHostAddress(); - } - catch (UnknownHostException e) - { - return "Unknown"; - } - } - - public String getHostName() - { - try - { - return InetAddress.getLocalHost().getHostName(); - } - catch (UnknownHostException e) - { - return "Unknown"; - } - } - - public RepoUsage getRestrictions() - { - return this.services.getRepoAdminService().getRestrictions(); - } - - public RepoUsage getUsage() - { - return this.services.getRepoAdminService().getUsage(); - } - - /** - * Gets the list of repository stores - * - * @return stores - */ - public List getStores() - { - return this.services.getNodeService().getStores(); - } -} +package org.alfresco.web.scripts; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.jscript.ScriptUtils; +import org.alfresco.repo.web.scripts.RepositoryContainer; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.repository.StoreRef; +import org.springframework.extensions.webscripts.WebScript; + +/** + * Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional + * Remote API methods using objects not available to base Repository project. + *

+ * See "web-scripts-application-context.xml" for bean definition. + * + * @since 4.2.0 + * @since 5.1 (Moved to Remote API project) + * @author Kevin Roast + */ +public class WebScriptUtils extends ScriptUtils +{ + protected RepositoryContainer repositoryContainer; + + public void setRepositoryContainer(RepositoryContainer repositoryContainer) + { + this.repositoryContainer = repositoryContainer; + } + + /** + * Searches for webscript components with the given family name. + * + * @param family the family + * + * @return An array of webscripts that match the given family name + */ + public Object[] findWebScripts(String family) + { + List values = new ArrayList(); + + for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) + { + if (family != null) + { + Set familys = webscript.getDescription().getFamilys(); + if (familys != null && familys.contains(family)) + { + values.add(webscript.getDescription()); + } + } + else + { + values.add(webscript.getDescription()); + } + } + + return values.toArray(new Object[0]); + } + + public String getHostAddress() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + return "Unknown"; + } + } + + public String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + return "Unknown"; + } + } + + public RepoUsage getRestrictions() + { + return this.services.getRepoAdminService().getRestrictions(); + } + + public RepoUsage getUsage() + { + return this.services.getRepoAdminService().getUsage(); + } + + /** + * Gets the list of repository stores + * + * @return stores + */ + public List getStores() + { + return this.services.getNodeService().getStores(); + } +} diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml index 8c965e2a36..ea1e191fdc 100644 --- a/remote-api/src/main/resources/alfresco/public-rest-context.xml +++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml @@ -942,8 +942,13 @@ + + + + + @@ -1063,12 +1068,21 @@ + + + + + + + + + diff --git a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java index 9d18221209..b22987a9f2 100644 --- a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -47,6 +47,7 @@ import org.junit.runners.Suite; org.alfresco.rest.api.tests.ActivitiesPostingTest.class, org.alfresco.rest.api.tests.AuthenticationsTest.class, org.alfresco.rest.api.tests.DiscoveryApiTest.class, + org.alfresco.rest.api.discovery.DiscoveryApiWebscriptUnitTest.class, org.alfresco.rest.api.tests.GroupsTest.class, org.alfresco.rest.api.tests.ModulePackagesApiTest.class, org.alfresco.rest.api.tests.NodeApiTest.class, diff --git a/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java index 9545280e76..4548e38d4c 100644 --- a/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java +++ b/remote-api/src/test/java/org/alfresco/AppContext04TestSuite.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2017 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -76,6 +76,7 @@ import org.junit.runners.Suite; org.alfresco.repo.web.scripts.site.SurfConfigTest.class, org.alfresco.repo.web.scripts.node.NodeWebScripTest.class, org.alfresco.rest.api.impl.CommentsImplUnitTest.class, + org.alfresco.rest.api.impl.RestApiDirectUrlConfigUnitTest.class }) public class AppContext04TestSuite { diff --git a/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java b/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java new file mode 100644 index 0000000000..394c13a8f1 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/discovery/DiscoveryApiWebscriptUnitTest.java @@ -0,0 +1,87 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api.discovery; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.alfresco.service.cmr.repository.ContentService; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +/** + * @author Mikołaj Brzeziński + */ +public class DiscoveryApiWebscriptUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private DiscoveryApiWebscript discoveryApiWebscript = mock(DiscoveryApiWebscript.class); + private RestApiDirectUrlConfig restApiDirectUrlConfig = mock(RestApiDirectUrlConfig.class); + private ContentService contentService = mock(ContentService.class); + + public void mockedAsserts(boolean restEnabled, boolean systemwideEnabled) + { + when(contentService.isContentDirectUrlEnabled()).thenReturn(systemwideEnabled); + when(restApiDirectUrlConfig.isEnabled()).thenReturn(restEnabled); + assertEquals(systemwideEnabled, contentService.isContentDirectUrlEnabled()); + assertEquals(restEnabled, restApiDirectUrlConfig.isEnabled()); + when(discoveryApiWebscript.isContentDirectUrlEnabled()).thenReturn(restEnabled && systemwideEnabled); + } + + @Test + public void testEnabledConfig_RestEnabledAndSystemwideEnabled() + { + mockedAsserts(ENABLED,ENABLED); + assertTrue("Direct Acess URLs are enabled",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestEnabledAndSystemwideDisabled() + { + mockedAsserts(ENABLED,DISABLED); + assertFalse("Direct Access URLs are disabled system-wide",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestDisabledAndSystemwideDisabled() + { + mockedAsserts(DISABLED,DISABLED); + assertFalse("REST API Direct Access URLs are disabled and Direct Access URLs are disabled system-wide ",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + + @Test + public void testDisabledConfig_RestDisabledAndSystemwideEnabled() + { + mockedAsserts(DISABLED,ENABLED); + assertFalse("REST API direct access URLs are disabled",discoveryApiWebscript.isContentDirectUrlEnabled()); + } + +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..e04c07338f --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/RestApiDirectUrlConfigUnitTest.java @@ -0,0 +1,132 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; +import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for REST API direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class RestApiDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 20L; + + private RestApiDirectUrlConfig restApiDirectUrlConfig; + + @Before + public void setup() + { + this.restApiDirectUrlConfig = new RestApiDirectUrlConfig(); + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(ENABLED); + sysConfig.setDefaultExpiryTimeInSec(30L); + sysConfig.setMaxExpiryTimeInSec(300L); + restApiDirectUrlConfig.setSystemWideDirectUrlConfig(sysConfig); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_DefaultExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, null); + + assertNull("Expected REST API default expiry time to be null", restApiDirectUrlConfig.getDefaultExpiryTimeInSec()); + restApiDirectUrlConfig.validate(); + Long expectedDefaultExpiryTime = restApiDirectUrlConfig.getSysWideDefaultExpiryTimeInSec(); + assertEquals("Expected REST API default expiry time to be set to the system-wide default", expectedDefaultExpiryTime, restApiDirectUrlConfig.getDefaultExpiryTimeInSec()); + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemMax() + { + Long systemMax = restApiDirectUrlConfig.getSysWideMaxExpiryTimeInSec(); + setupDirectAccessConfig(ENABLED, systemMax + 1); + + assertTrue("Expected REST API direct URLs to be enabled", restApiDirectUrlConfig.isEnabled()); + restApiDirectUrlConfig.validate(); + assertFalse("Expected REST API direct URLs to be disabled", restApiDirectUrlConfig.isEnabled()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime) + { + restApiDirectUrlConfig.setEnabled(isEnabled); + restApiDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + } +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java index 4938ecf42e..5ca4b778a2 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -68,6 +68,7 @@ import org.junit.After; import org.junit.Before; import org.junit.experimental.categories.Category; import org.springframework.util.ResourceUtils; +import org.alfresco.service.cmr.repository.DirectAccessUrl; import java.io.ByteArrayInputStream; import java.io.File; @@ -136,6 +137,8 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi protected static PersonService personService; protected final String RUNID = System.currentTimeMillis()+""; + + private static final String REQUEST_NODE_DIRECT_ACCESS_URL = "requestNodeDirectAccessUrl"; @Override @Before @@ -211,6 +214,11 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi setRequestContext(null); } + protected String getRequestContentDirectUrl(String nodeId) + { + return URL_NODES + "/" + nodeId + "/" + REQUEST_NODE_DIRECT_ACCESS_URL; + } + /** * The api scope. either public or private * diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java index 39c86ed916..2eb0f15df9 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java @@ -26,23 +26,23 @@ */ package org.alfresco.rest.api.tests; -import org.alfresco.repo.web.scripts.BufferedResponse; -import org.alfresco.repo.web.scripts.TempOutputStream; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; -import org.alfresco.util.TempFileProvider; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.file.Files; import java.util.Arrays; +import java.util.function.Supplier; import java.util.stream.Stream; +import org.alfresco.repo.web.scripts.BufferedResponse; +import org.alfresco.repo.web.scripts.TempOutputStream; +import org.alfresco.util.TempFileProvider; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + /** * Test that BufferedResponse uses a temp file instead of buffering the entire output stream in memory * @@ -82,17 +82,25 @@ public class BufferedResponseTest public void testOutputStream() throws IOException { File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME); - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false,true); - BufferedResponse response = new BufferedResponse(null, 0, streamFactory); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, + MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); - long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX ); - copyFileToOutputStream(response); - long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); - - response.getOutputStream().close(); + final long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); - Assert.assertEquals(countBefore + 1, countAfter); + try (BufferedResponse response = new BufferedResponse(null, 0, streamFactory)) + { + copyFileToOutputStream(response); + final long countBeforeClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + response.getOutputStream().close(); + final long countAfterClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + + Assert.assertEquals(countBefore + 1, countBeforeClose); + Assert.assertEquals(countBefore + 1, countAfterClose); + } + + final long countAfterDestroy = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); + Assert.assertEquals(countBefore, countAfterDestroy); } private void copyFileToOutputStream(BufferedResponse response) throws IOException diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java index 6cfbdb9885..f244401350 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/DiscoveryApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -185,6 +185,7 @@ public class DiscoveryApiTest extends AbstractSingleNetworkSiteTest assertTrue(statusInfo.getIsAuditEnabled()); assertTrue(statusInfo.getIsQuickShareEnabled()); assertTrue(statusInfo.getIsThumbnailGenerationEnabled()); + assertFalse(statusInfo.getIsDirectAccessUrlEnabled()); // Check modules List modulePackageList = repositoryInfo.getModules(); diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java index 247a60df9b..a5bdef067f 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -6250,5 +6250,33 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest assertFalse((Boolean) constraintParameters.get("requiresMatch")); } + @Test + public void testRequestContentDirectUrl() throws Exception + { + setRequestContext(user1); + + // Use existing test file + String fileName = "quick-1.txt"; + File file = getResourceFile(fileName); + + MultiPartBuilder multiPartBuilder = MultiPartBuilder.create().setFileData(new MultiPartBuilder.FileData(fileName, file)); + MultiPartBuilder.MultiPartRequest reqBody = multiPartBuilder.build(); + + // Upload text content + HttpResponse response = post(getNodeChildrenUrl(Nodes.PATH_MY), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + final String contentNodeId = document.getId(); + + // Check the upload response + assertEquals(fileName, document.getName()); + ContentInfo contentInfo = document.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + + + getSingle(getRequestContentDirectUrl(contentNodeId), null, null, null, 405); + } + } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java index ae7b475f99..653e9256d8 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java @@ -33,11 +33,11 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.function.Supplier; import java.util.stream.Stream; import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.web.scripts.TempOutputStream; -import org.alfresco.repo.web.scripts.TempOutputStreamFactory; import org.alfresco.util.TempFileProvider; import org.junit.Assert; import org.junit.Test; @@ -57,11 +57,12 @@ public class TempOutputStreamTest @Test public void testInMemoryStream() throws IOException { - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, + MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); File file = createTextFileWithRandomContent(MEMORY_THRESHOLD - 1024L); { - TempOutputStream outputStream = streamFactory.createOutputStream(); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -83,8 +84,8 @@ public class TempOutputStreamTest { // Create stream factory that doesn't delete temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); - TempOutputStream outputStream = streamFactory.createOutputStream(); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -107,26 +108,6 @@ public class TempOutputStreamTest Assert.assertEquals(countBefore, countAfter); } - { - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); - - long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); - - StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream); - - // Check that temp file was created - long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); - Assert.assertEquals(countBefore + 1, countAfter); - - outputStream.close(); - - // Check that file was deleted on close - countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); - Assert.assertEquals(countBefore, countAfter); - } - file.delete(); } @@ -140,9 +121,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(contentSize); - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + // Create stream factory that deletes the temp file when the max Size is reached + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -156,7 +137,7 @@ public class TempOutputStreamTest // Expected } - // Check that file was already deleted on close + // Check that file was already deleted on error long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); Assert.assertEquals(countBefore, countAfter); @@ -170,9 +151,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(contentSize); - // Create stream factory that deletes temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + // Create stream factory that deletes the temp file when the max Size is reached + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -186,7 +167,7 @@ public class TempOutputStreamTest // Expected } - // Check that file was already deleted on close + // Check that file was already deleted on error long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); Assert.assertEquals(countBefore, countAfter); @@ -200,9 +181,9 @@ public class TempOutputStreamTest File file = createTextFileWithRandomContent(MEMORY_THRESHOLD + 1024L); // Create stream factory that doesn't delete temp file on stream close - TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true, false); + Supplier streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true); - TempOutputStream outputStream = streamFactory.createOutputStream(); + TempOutputStream outputStream = streamFactory.get(); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); @@ -220,7 +201,7 @@ public class TempOutputStreamTest // Compare content String contentWriten = StreamUtils.copyToString(new BufferedInputStream(new FileInputStream(file)), Charset.defaultCharset()); - String contentRead = StreamUtils.copyToString(outputStream.getInputStream(), Charset.defaultCharset()); + String contentRead = StreamUtils.copyToString(outputStream.toNewInputStream(), Charset.defaultCharset()); Assert.assertEquals(contentWriten, contentRead); outputStream.destroy(); diff --git a/repository/pom.xml b/repository/pom.xml index c8f3898f3d..be2d8e213e 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -7,7 +7,7 @@ org.alfresco alfresco-community-repo - 11.95-SNAPSHOT + 11.105-SNAPSHOT diff --git a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java index 86e58e6527..158306bed0 100644 --- a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,12 +25,21 @@ */ package org.alfresco.repo.content; +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; @@ -47,6 +56,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.MimetypeServiceAware; import org.alfresco.service.cmr.repository.NodeRef; @@ -62,13 +72,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.extensions.surf.util.I18NUtil; -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - /** * Service implementation acting as a level of indirection between the client * and the underlying content store. @@ -82,7 +85,7 @@ import java.util.Set; */ public class ContentServiceImpl implements ContentService, ApplicationContextAware { - private static Log logger = LogFactory.getLog(ContentServiceImpl.class); + private static final Log logger = LogFactory.getLog(ContentServiceImpl.class); private DictionaryService dictionaryService; private NodeService nodeService; @@ -99,6 +102,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa /** Should we consider zero byte content to be the same as no content? */ private boolean ignoreEmptyContent; + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + /** * The policy component */ @@ -140,7 +145,12 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa { this.store = store; } - + + public void setSystemWideDirectUrlConfig(SystemWideDirectUrlConfig systemWideDirectUrlConfig) + { + this.systemWideDirectUrlConfig = systemWideDirectUrlConfig; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; @@ -510,23 +520,10 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa return tempStore.getWriter(ContentContext.NULL_CONTEXT); } - @Override + @Deprecated public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt) { - ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); - - // check that the URL is available - if (contentData == null || contentData.getContentUrl() == null) - { - throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); - } - - if (store.isDirectAccessSupported()) - { - return store.getDirectAccessUrl(contentData.getContentUrl(), expiresAt); - } - - return null; + return requestContentDirectUrl(nodeRef, true, null); } /** @@ -586,4 +583,104 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } } } + + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled() + { + return systemWideDirectUrlConfig.isEnabled() && store.isContentDirectUrlEnabled(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + boolean contentDirectUrlEnabled = false; + + // TODO: update this + if (systemWideDirectUrlConfig.isEnabled()) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + contentDirectUrlEnabled = (store.isContentDirectUrlEnabled(nodeRef)); + } + + return contentDirectUrlEnabled; + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + if (!systemWideDirectUrlConfig.isEnabled()) + { + throw new DirectAccessUrlDisabledException("Direct access url isn't available."); + } + + String contentUrl = getContentUrl(nodeRef); + String fileName = getFileName(nodeRef); + validFor = adjustValidFor(validFor); + + DirectAccessUrl directAccessUrl = null; + if (store.isContentDirectUrlEnabled()) + { + try + { + directAccessUrl = store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); + } + catch (UnsupportedOperationException ex) + { + // expected exception + } + } + return directAccessUrl; + } + + protected String getContentUrl(NodeRef nodeRef) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + return contentData.getContentUrl(); + } + + protected String getFileName(NodeRef nodeRef) + { + String fileName = null; + + try + { + fileName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + } + catch (InvalidNodeRefException ex) + { + } + + return fileName; + } + + private Long adjustValidFor(Long validFor) + { + if (validFor == null || validFor > systemWideDirectUrlConfig.getDefaultExpiryTimeInSec()) + { + validFor = systemWideDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + return validFor; + } } \ No newline at end of file diff --git a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java index 5cad001984..77e7851547 100644 --- a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,7 +25,6 @@ */ package org.alfresco.repo.content.caching; -import java.util.Date; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; @@ -103,7 +103,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis { eventPublisher.publishEvent(new CachingContentStoreCreatedEvent(this)); } - + @Override public boolean isContentUrlSupported(String contentUrl) { @@ -137,7 +137,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis /** * {@inheritDoc} *

- * For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists. + * For {@link FileContentStore#SPOOF_PROTOCOL spoofed} URLs, the URL always exists. */ @Override public boolean exists(String contentUrl) @@ -478,13 +478,35 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis return this.beanName; } - public boolean isDirectAccessSupported() + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled() { - return backingStore.isDirectAccessSupported(); + return backingStore.isContentDirectUrlEnabled(); } - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) { - return backingStore.getDirectAccessUrl(contentUrl, expiresAt); + return backingStore.isContentDirectUrlEnabled(nodeRef); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } } diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java new file mode 100644 index 0000000000..db6ebef9e6 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/AbstractDirectUrlConfig.java @@ -0,0 +1,81 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +/** + * Direct Access Url configuration settings. + * + * @author Sara Aspery + */ +public abstract class AbstractDirectUrlConfig implements DirectUrlConfig +{ + /** System-wide direct access URL configuration */ + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + + /** Direct access URL configuration settings */ + private Boolean enabled; + private Long defaultExpiryTimeInSec; + + public void setSystemWideDirectUrlConfig(SystemWideDirectUrlConfig systemWideDirectUrlConfig) + { + this.systemWideDirectUrlConfig = systemWideDirectUrlConfig; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public void setDefaultExpiryTimeInSec(Long defaultExpiryTimeInSec) + { + this.defaultExpiryTimeInSec = defaultExpiryTimeInSec; + } + + protected Boolean isSysWideEnabled() + { + return systemWideDirectUrlConfig.isEnabled(); + } + + public Long getSysWideDefaultExpiryTimeInSec() + { + return systemWideDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + + public Long getSysWideMaxExpiryTimeInSec() + { + return systemWideDirectUrlConfig.getMaxExpiryTimeInSec(); + } + + public Boolean isEnabled() + { + return enabled; + } + + public Long getDefaultExpiryTimeInSec() + { + return defaultExpiryTimeInSec; + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java new file mode 100644 index 0000000000..79494718bf --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfig.java @@ -0,0 +1,134 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content store direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class ContentStoreDirectUrlConfig extends AbstractDirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(ContentStoreDirectUrlConfig.class); + + private Long maxExpiryTimeInSec; + + public void setMaxExpiryTimeInSec(Long maxExpiryTimeInSec) + { + this.maxExpiryTimeInSec = maxExpiryTimeInSec; + } + + public Long getMaxExpiryTimeInSec() + { + return maxExpiryTimeInSec; + } + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs for the content store if any error found in the content store direct access URL config + try + { + validateDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling content store direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + } + + /* Helper method to validate the content direct access url configuration settings */ + private void validateDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getMaxExpiryTimeInSec() == null) + { + logger.warn(String.format("Maximum expiry time property is missing: setting to system-wide maximum [%s].", getSysWideMaxExpiryTimeInSec())); + setMaxExpiryTimeInSec(getSysWideMaxExpiryTimeInSec()); + } + else if (getMaxExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL maximum expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getMaxExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() == null) + { + logger.warn(String.format("Default expiry time property is missing: setting to system-wide default [%s].", getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + else if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + logger.warn(String.format("Default expiry time property [%s] exceeds maximum expiry time for content store [%s]: setting to system-wide default [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec(), getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + else if (getDefaultExpiryTimeInSec() > getSysWideDefaultExpiryTimeInSec()) + { + logger.warn(String.format("Default expiry time property [%s] exceeds system-wide default expiry time [%s]: setting to system-wide default.", + getDefaultExpiryTimeInSec(), getSysWideDefaultExpiryTimeInSec())); + setDefaultExpiryTimeInSec(getSysWideDefaultExpiryTimeInSec()); + } + + if (getDefaultExpiryTimeInSec() < 1) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] is invalid.", getDefaultExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getSysWideMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] exceeds system-wide maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getSysWideMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + + if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + String errorMsg = String.format("Content store direct access URL default expiry time [%s] exceeds content store maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java new file mode 100644 index 0000000000..d0702b398d --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Runtime exception thrown when direct access URLs are disabled. + * + * @author Sara Aspery + */ +public class DirectAccessUrlDisabledException extends AlfrescoRuntimeException +{ + + private static final long serialVersionUID = -6506082117146782993L; + + public DirectAccessUrlDisabledException(String msg) + { + super(msg); + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java new file mode 100644 index 0000000000..68ed3a84da --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectUrlConfig.java @@ -0,0 +1,41 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.api.AlfrescoPublicApi; + +/** + * Direct Access Url configuration settings interface. + * + * @author Sara Aspery + */ +@AlfrescoPublicApi +public interface DirectUrlConfig +{ + Boolean isEnabled(); + Long getDefaultExpiryTimeInSec(); + void validate(); +} diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java b/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java new file mode 100644 index 0000000000..5c6445ed5f --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/InvalidDirectAccessUrlConfigException.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Runtime exception thrown when the direct access URL configuration settings are invalid. + * + * @author Sara Aspery + */ +public class InvalidDirectAccessUrlConfigException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -6318313836484979887L; + + public InvalidDirectAccessUrlConfigException(String msg) + { + super(msg); + } +} + diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java b/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java new file mode 100644 index 0000000000..ecfa769e38 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfig.java @@ -0,0 +1,124 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * System-wide direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class SystemWideDirectUrlConfig implements DirectUrlConfig +{ + private static final Log logger = LogFactory.getLog(SystemWideDirectUrlConfig.class); + + /** Direct access url configuration settings */ + private Boolean enabled; + private Long defaultExpiryTimeInSec; + private Long maxExpiryTimeInSec; + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public void setDefaultExpiryTimeInSec(Long defaultExpiryTimeInSec) + { + this.defaultExpiryTimeInSec = defaultExpiryTimeInSec; + } + + public void setMaxExpiryTimeInSec(Long maxExpiryTimeInSec) + { + this.maxExpiryTimeInSec = maxExpiryTimeInSec; + } + + public Boolean isEnabled() + { + return enabled; + } + + public Long getDefaultExpiryTimeInSec() + { + return defaultExpiryTimeInSec; + } + + public Long getMaxExpiryTimeInSec() + { + return maxExpiryTimeInSec; + } + + /** + * Configuration initialise + */ + public void init() + { + validate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void validate() + { + // Disable direct access URLs system-wide if any error found in the system-wide direct access URL config + try + { + validateSystemDirectAccessUrlConfig(); + } + catch (InvalidDirectAccessUrlConfigException ex) + { + logger.error("Disabling system-wide direct access URLs due to configuration error: " + ex.getMessage()); + setEnabled(false); + } + } + + /* Helper method to validate the system-wide direct access url configuration settings */ + private void validateSystemDirectAccessUrlConfig() throws InvalidDirectAccessUrlConfigException + { + if (isEnabled()) + { + if (getDefaultExpiryTimeInSec() == null || getDefaultExpiryTimeInSec() < 1) + { + throw new InvalidDirectAccessUrlConfigException("System-wide direct access URL default expiry time is missing or invalid."); + } + + if (getMaxExpiryTimeInSec() == null || getMaxExpiryTimeInSec() < 1) + { + throw new InvalidDirectAccessUrlConfigException("System-wide direct access URL maximum expiry time is missing or invalid."); + } + + if (getDefaultExpiryTimeInSec() > getMaxExpiryTimeInSec()) + { + String errorMsg = String.format("System-wide direct access URL default expiry time [%s] exceeds maximum expiry time [%s].", + getDefaultExpiryTimeInSec(), getMaxExpiryTimeInSec()); + throw new InvalidDirectAccessUrlConfigException(errorMsg); + } + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java index 15cc9c5246..6c700e0572 100644 --- a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,7 +25,6 @@ */ package org.alfresco.repo.content.replication; -import java.util.Date; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -65,7 +65,7 @@ import org.apache.commons.logging.LogFactory; */ public class AggregatingContentStore extends AbstractContentStore { - private static Log logger = LogFactory.getLog(AggregatingContentStore.class); + private static final Log logger = LogFactory.getLog(AggregatingContentStore.class); private ContentStore primaryStore; private List secondaryStores; @@ -266,33 +266,58 @@ public class AggregatingContentStore extends AbstractContentStore } /** - * @return Returns true if at least one store supports direct access + * @return Returns {@code true} if at least one store supports direct access URLs */ - public boolean isDirectAccessSupported() + public boolean isContentDirectUrlEnabled() { // Check the primary store - boolean isDirectAccessSupported = primaryStore.isDirectAccessSupported(); + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(); - if (!isDirectAccessSupported) + if (!isContentDirectUrlEnabled) { // Direct access is not supported by the primary store so we have to check the // other stores for (ContentStore store : secondaryStores) { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(); - isDirectAccessSupported = store.isDirectAccessSupported(); - - if (isDirectAccessSupported) + if (isContentDirectUrlEnabled) { break; } } } - return isDirectAccessSupported; + return isContentDirectUrlEnabled; } - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * @return Returns {@code true} if at least one store supports direct access URL for node + */ + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + // Check the primary store + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(nodeRef); + + if (!isContentDirectUrlEnabled) + { + // Direct access is not supported by the primary store so we have to check the + // other stores + for (ContentStore store : secondaryStores) + { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(nodeRef); + + if (isContentDirectUrlEnabled) + { + break; + } + } + } + + return isContentDirectUrlEnabled; + } + + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) { if (primaryStore == null) { @@ -312,13 +337,13 @@ public class AggregatingContentStore extends AbstractContentStore // Check the primary store try { - directAccessUrl = primaryStore.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = primaryStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { // The store does not support direct access URL directAccessUrlSupported = false; - } + } catch (UnsupportedContentUrlException e) { // The store can't handle the content URL @@ -335,7 +360,7 @@ public class AggregatingContentStore extends AbstractContentStore { try { - directAccessUrl = store.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { diff --git a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java index 81622ff41a..ce73035374 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,13 +25,13 @@ */ package org.alfresco.service.cmr.repository; +import java.util.Date; + import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.service.Auditable; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.namespace.QName; -import java.util.Date; - /** * Provides methods for accessing and transforming content. *

@@ -168,6 +168,48 @@ public interface ContentService * @return A direct access URL object for a binary content or returns null if not supported * @throws IllegalArgumentException if there is no binary content for the node */ + @Deprecated @Auditable(parameters = {"nodeRef", "expiresAt"}) public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt); + + /** + * Checks if the system and at least one store supports the retrieving of direct access URLs. + * + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise + */ + boolean isContentDirectUrlEnabled(); + + /** + * Checks if the system and store supports the retrieving of a direct access {@code URL} for the given node. + * + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise + */ + boolean isContentDirectUrlEnabled(NodeRef nodeRef); + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment) + { + return requestContentDirectUrl(nodeRef, attachment, null); + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + @Auditable(parameters = {"nodeRef", "validFor"}) + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); } diff --git a/repository/src/main/resources/alfresco/content-services-context.xml b/repository/src/main/resources/alfresco/content-services-context.xml index aed14cdd9e..a086db2e41 100644 --- a/repository/src/main/resources/alfresco/content-services-context.xml +++ b/repository/src/main/resources/alfresco/content-services-context.xml @@ -1,359 +1,376 @@ - - - - - - - false - - - ContentStore - - - - ${filecontentstore.subsystem.name} - - - - manager - - - - - - - - true - - - ContentStore - - - unencrypted - - - - managed - unencrypted - - - - - - - - - - - fileContentStore - - - - org.alfresco.repo.content.ContentStore - org.alfresco.repo.content.ContentStoreCaps - - - - - - - - - - - - - - - - ${dir.contentstore.deleted} - - - - - - - - - - - - - - - - - - - - - - ${system.content.orphanProtectDays} - - - ${system.content.deletionFailureAction} - - - - - - - - - - - - - - - - - - - - - ${system.content.cleanerBatchSize} - - - - - - ${system.content.eagerOrphanCleanup} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${policy.content.update.ignoreEmpty} - - - - - - - - - - - - - - - - - - UTF-8 - - - - - - - - - - - - - - - - - - - - - - - classpath:alfresco/mimetype/mimetype-map.xml - classpath:alfresco/mimetype/mimetype-map-openoffice.xml - classpath*:alfresco/module/*/mimetype-map*.xml - classpath*:alfresco/extension/mimetype/*-map.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - classpath:alfresco/ml/content-filter-lang.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EEE, d MMM yyyy HH:mm:ss Z - EEE, d MMM yy HH:mm:ss Z - d MMM yyyy HH:mm:ss Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.content.transform.TransformerConfigMBean - - - - - - - - - - - transformerDebugLog - - - - org.apache.commons.logging.Log - - - - - - - - - - - transformerLog - - - - org.apache.commons.logging.Log - - - - - - + + + + + + + false + + + ContentStore + + + + ${filecontentstore.subsystem.name} + + + + manager + + + + + + + + true + + + ContentStore + + + unencrypted + + + + managed + unencrypted + + + + + + + + + + + fileContentStore + + + + org.alfresco.repo.content.ContentStore + org.alfresco.repo.content.ContentStoreCaps + + + + + + + + + + + + + + + + ${dir.contentstore.deleted} + + + + + + + + + + + + + + + + + + + + + + ${system.content.orphanProtectDays} + + + ${system.content.deletionFailureAction} + + + + + + + + + + + + + + + + + + + + + ${system.content.cleanerBatchSize} + + + + + + ${system.content.eagerOrphanCleanup} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${policy.content.update.ignoreEmpty} + + + + + + + + + + + + + + + + + + + + + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + classpath:alfresco/mimetype/mimetype-map.xml + classpath:alfresco/mimetype/mimetype-map-openoffice.xml + classpath*:alfresco/module/*/mimetype-map*.xml + classpath*:alfresco/extension/mimetype/*-map.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + classpath:alfresco/ml/content-filter-lang.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EEE, d MMM yyyy HH:mm:ss Z + EEE, d MMM yy HH:mm:ss Z + d MMM yyyy HH:mm:ss Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.content.transform.TransformerConfigMBean + + + + + + + + + + + transformerDebugLog + + + + org.apache.commons.logging.Log + + + + + + + + + + + transformerLog + + + + org.apache.commons.logging.Log + + + + + + + + + + + + + + + + + + + + diff --git a/repository/src/main/resources/alfresco/public-services-security-context.xml b/repository/src/main/resources/alfresco/public-services-security-context.xml index 0129319643..b6e32f9d37 100644 --- a/repository/src/main/resources/alfresco/public-services-security-context.xml +++ b/repository/src/main/resources/alfresco/public-services-security-context.xml @@ -494,8 +494,9 @@ org.alfresco.service.cmr.repository.ContentService.getRawReader=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.repository.ContentService.getReader=ACL_NODE.0.sys:base.ReadContent org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent - org.alfresco.service.cmr.repository.ContentService.getDirectAccessUrl=ACL_NODE.0.sys:base.ReadContent org.alfresco.service.cmr.repository.ContentService.getTempWriter=ACL_ALLOW + org.alfresco.service.cmr.repository.ContentService.requestContentDirectUrl=ACL_NODE.0.sys:base.ReadContent + org.alfresco.service.cmr.repository.ContentService.isContentDirectUrlEnabled=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.*=ACL_DENY diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index ef6bade8a1..b95cbf72ae 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -1,1247 +1,1310 @@ -# Repository configuration - -repository.name=Main Repository - -# Schema number -version.schema=15001 - -# Directory configuration - -dir.root=./alf_data - -dir.contentstore=${dir.root}/contentstore -dir.contentstore.deleted=${dir.root}/contentstore.deleted -dir.contentstore.bucketsPerMinute=0 - -# ContentStore subsystem: default choice -filecontentstore.subsystem.name=unencryptedContentStore - -# The location of cached content -dir.cachedcontent=${dir.root}/cachedcontent - -# The value for the maximum permitted size in bytes of all content. -# No value (or a negative long) will be taken to mean that no limit should be applied. -# See content-services-context.xml -system.content.maximumFileSizeLimit= - -# -# The server mode. Set value in alfresco-global.properties -# UNKNOWN | TEST | BACKUP | PRODUCTION -# -system.serverMode=UNKNOWN - -# The location for lucene index files -dir.indexes=${dir.root}/lucene-indexes - -# The location for index backups -dir.indexes.backup=${dir.root}/backup-lucene-indexes - -# The location for lucene index locks -dir.indexes.lock=${dir.indexes}/locks - -#Directory to find external license -dir.license.external=. -# Spring resource location of external license files -location.license.external=file://${dir.license.external}/*.lic -# Spring resource location of embedded license files -location.license.embedded=/WEB-INF/alfresco/license/*.lic -# Spring resource location of license files on shared classpath -location.license.shared=classpath*:/alfresco/extension/license/*.lic - -# WebDAV initialization properties -system.webdav.servlet.enabled=true -system.webdav.url.path.prefix= -system.webdav.storeName=${protocols.storeName} -system.webdav.rootPath=${protocols.rootPath} -# File name patterns that trigger rename shuffle detection -# pattern is used by move - tested against full path after it has been lower cased. -system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*atmp[0-9]+$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$)|(.*\\.sb\\-\\w{8}\\-\\w{6}$) -system.webdav.activities.enabled=false - - -system.workflow.jbpm.comment.property.max.length=-1 -system.workflow.comment.property.max.length=4000 - -#Determines if Activiti definitions are visible -system.workflow.engine.activiti.definitions.visible=true - - -# Determines if the Activiti engine is enabled -system.workflow.engine.activiti.enabled=true -system.workflow.engine.activiti.idblocksize=100 -system.workflow.engine.activiti.taskvariableslimit=20000 - -# Determines if the workflows that are deployed to the activiti engine should -# be deployed in the tenant-context of the thread IF the tenant-service is enabled -# If set to false, all workflows deployed will be shared among tenants. Recommended -# setting is true unless there is a good reason to not allow deploy tenant-specific -# worklfows when a MT-environment is set up. -system.workflow.deployWorkflowsInTenant=true -#Determines if historic process instance are retained in case of canceling a process instance -system.workflow.engine.activiti.retentionHistoricProcessInstance=false - -# The maximum number of groups to check for pooled tasks. For performance -# reasons, this is limited to 500 by default. -system.workflow.maxAuthoritiesForPooledTasks=500 - -# The maximum number of pooled tasks to return in a query. It may be necessary -# to limit this depending on UI limitations. -system.workflow.maxPooledTasks=-1 - -# The maximum number of reviewers for "Group Review and Approve" workflow. -# Use '0' for unlimited. -system.workflow.maxGroupReviewers=0 - -index.subsystem.name=noindex - -# ######################################### # -# Index Tracking Configuration # -# ######################################### # -# -# Index tracking information of a certain age is cleaned out by a scheduled job. -# Any clustered system that has been offline for longer than this period will need to be seeded -# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. -# Use -1 to disable purging. This can be switched on at any stage. -index.tracking.minRecordPurgeAgeDays=30 -# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size -# of the chunk (in ms). Default is a couple of hours. -index.tracking.purgeSize=7200000 - -# Change the failure behaviour of the configuration checker -system.bootstrap.config_check.strict=true - - -# -# How long should shutdown wait to complete normally before -# taking stronger action and calling System.exit() -# in ms, 10,000 is 10 seconds -# -shutdown.backstop.timeout=10000 -shutdown.backstop.enabled=false - -# Server Single User Mode -# note: -# only allow named user (note: if blank or not set then will allow all users) -# assuming maxusers is not set to 0 -#server.singleuseronly.name=admin - -# Server Max Users - limit number of users with non-expired tickets -# note: -# -1 allows any number of users, assuming not in single-user mode -# 0 prevents further logins, including the ability to enter single-user mode -server.maxusers=-1 - -# -# Disable all shared caches (mutable and immutable) -# These properties are used for diagnostic purposes -system.cache.disableMutableSharedCaches=false -system.cache.disableImmutableSharedCaches=false - -# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) -system.cache.parentAssocs.maxSize=130000 - -# The average number of parents expected per cache entry. This parameter is multiplied by the above -# value to compute a limit on the total number of cached parents, which will be proportional to the -# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive -# memory usage. -system.cache.parentAssocs.limitFactor=8 - -# -# Properties to limit resources spent on individual searches -# -# The maximum time spent pruning results -system.acl.maxPermissionCheckTimeMillis=10000 -# The maximum number of search results to perform permission checks against -system.acl.maxPermissionChecks=1000 -system.acl.maxPermissionCheckEnabled=false - -# The maximum number of filefolder list results -system.filefolderservice.defaultListMaxResults=5000 -# DEPRECATED: Use 'system.auditableData.preserve' -system.preserve.modificationData=false -# The default to preserve all cm:auditable data on a node when the process is not directly driven by a user action -system.auditableData.preserve=${system.preserve.modificationData} -# Specific control of how the FileFolderService treats cm:auditable data when performing moves -system.auditableData.FileFolderService=${system.auditableData.preserve} -# Specific control of whether ACL changes on a node trigger the cm:auditable aspect -system.auditableData.ACLs=${system.auditableData.preserve} - -# Properties to control read permission evaluation for acegi -system.readpermissions.optimise=true -system.readpermissions.bulkfetchsize=1000 - -# -# Manually control how the system handles maximum string lengths. -# Any zero or negative value is ignored. -# Only change this after consulting support or reading the appropriate Javadocs for -# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2. -# Before database migration, the string value storage may need to be adjusted using the scheduled job -system.maximumStringLength=-1 -system.maximumStringLength.jobCronExpression=* * * * * ? 2099 -system.maximumStringLength.jobQueryRange=10000 -system.maximumStringLength.jobThreadCount=4 - -# -# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation -# - hibernate works as is up to this size -# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance -# events may not group if there are post action listener registered (this is not the case with the default distribution) -system.hibernateMaxExecutions=20000 - -# -# Determine if modification timestamp propagation from child to parent nodes is respected or not. -# Even if 'true', the functionality is only supported for child associations that declare the -# 'propagateTimestamps' element in the dictionary definition. -system.enableTimestampPropagation=true - -# -# Enable system model integrity checking. -# WARNING: Changing this is unsupported; bugs may corrupt data -system.integrity.enabled=true -# Do integrity violations fail transactions -# WARNING: Changing this is unsupported; bugs may corrupt data -system.integrity.failOnViolation=true -# The number of errors to report when violations are detected -system.integrity.maxErrorsPerTransaction=5 -# Add call stacks to integrity events so that errors are logged with possible causes -# WARNING: This is expensive and should only be switched on for diagnostic purposes -system.integrity.trace=false - -# -# Decide if content should be removed from the system immediately after being orphaned. -# Do not change this unless you have examined the impact it has on your backup procedures. -system.content.eagerOrphanCleanup=false -# The number of days to keep orphaned content in the content stores. -# This has no effect on the 'deleted' content stores, which are not automatically emptied. -system.content.orphanProtectDays=14 -# The action to take when a store or stores fails to delete orphaned content -# IGNORE: Just log a warning. The binary remains and the record is expunged -# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. -system.content.deletionFailureAction=IGNORE -# The CRON expression to trigger the deletion of resources associated with orphaned content. -system.content.orphanCleanup.cronExpression=0 0 4 * * ? -# The batch size user by the content store cleaner -system.content.cleanerBatchSize=1000 - -# The CRON expression to trigger the cleanup of deleted nodes and dangling transactions that are old enough -system.nodeServiceCleanup.cronExpression=0 0 21 * * ? - -# When transforming archive files (.zip etc) into text representations (such as -# for full text indexing), should the files within the archive be processed too? -# If enabled, transformation takes longer, but searches of the files find more. -transformer.Archive.includeContents=false - -# Database configuration -db.schema.name= -db.schema.stopAfterSchemaBootstrap=false -db.schema.update=true -db.schema.update.lockRetryCount=24 -db.schema.update.lockRetryWaitSeconds=5 -db.driver=org.gjt.mm.mysql.Driver -db.name=alfresco -db.url=jdbc:mysql:///${db.name} -db.username=alfresco -db.password=alfresco -db.pool.initial=10 -db.pool.max=275 -db.txn.isolation=-1 -db.pool.statements.enable=true -db.pool.statements.max=40 -db.pool.min=10 -db.pool.idle=10 -db.pool.wait.max=5000 - -db.pool.validate.query= -db.pool.evict.interval=600000 -db.pool.evict.idle.min=1800000 -# -# note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) -# and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) -# -# * The number of objects to examine during each run of the idle object evictor thread (if any). -# * When a negative value is supplied, ceil({@link #getNumIdle})/abs({@link #getNumTestsPerEvictionRun}) -# * tests will be run. I.e., when the value is -n, roughly one nth of the -# * idle objects will be tested per run. -# -db.pool.evict.num.tests=-1 - -db.pool.evict.validate=false -db.pool.validate.borrow=true -db.pool.validate.return=false - -db.pool.abandoned.detect=false -db.pool.abandoned.time=300 -# -# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) -# and also requires db.pool.abandoned.detect=true (removeAbandoned) -# -db.pool.abandoned.log=false - - -# Audit configuration -audit.enabled=true -audit.tagging.enabled=true -audit.alfresco-access.enabled=false -audit.alfresco-access.sub-actions.enabled=false -audit.cmischangelog.enabled=false -audit.dod5015.enabled=false -# Setting this flag to true will force startup failure when invalid audit configurations are detected -audit.config.strict=false -# Audit map filter for AccessAuditor - restricts recorded events to user driven events -audit.filter.alfresco-access.default.enabled=false -audit.filter.alfresco-access.transaction.user=~System;~null;.* -audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site -audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* - - -# System Configuration -system.store=system://system -system.descriptor.childname=sys:descriptor -system.descriptor.current.childname=sys:descriptor-current - -# User config -alfresco_user_store.store=user://alfrescoUserStore -alfresco_user_store.system_container.childname=sys:system -alfresco_user_store.user_container.childname=sys:people - -# note: default admin username - should not be changed after installation -alfresco_user_store.adminusername=admin - -# Initial password - editing this will not have any effect once the repository is installed -alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 - -# note: default guest username - should not be changed after installation -alfresco_user_store.guestusername=guest - -# Used to move home folders to a new location -home_folder_provider_synchronizer.enabled=false -home_folder_provider_synchronizer.override_provider= -home_folder_provider_synchronizer.keep_empty_parents=false - -# Spaces Archive Configuration -spaces.archive.store=archive://SpacesStore - -# Spaces Configuration -spaces.store=workspace://SpacesStore -spaces.company_home.childname=app:company_home -spaces.guest_home.childname=app:guest_home -spaces.dictionary.childname=app:dictionary -spaces.templates.childname=app:space_templates -spaces.imap_attachments.childname=cm:Imap Attachments -spaces.imap_home.childname=cm:Imap Home -spaces.imapConfig.childname=app:imap_configs -spaces.imap_templates.childname=app:imap_templates -spaces.scheduled_actions.childname=cm:Scheduled Actions -spaces.emailActions.childname=app:email_actions -spaces.searchAction.childname=cm:search -spaces.templates.content.childname=app:content_templates -spaces.templates.email.childname=app:email_templates -spaces.templates.email.invite1.childname=app:invite_email_templates -spaces.templates.email.notify.childname=app:notify_email_templates -spaces.templates.email.following.childname=app:following -spaces.templates.rss.childname=app:rss_templates -spaces.savedsearches.childname=app:saved_searches -spaces.scripts.childname=app:scripts -spaces.content_forms.childname=app:forms -spaces.user_homes.childname=app:user_homes -spaces.user_homes.regex.key=userName -spaces.user_homes.regex.pattern= -spaces.user_homes.regex.group_order= -spaces.sites.childname=st:sites -spaces.templates.email.invite.childname=cm:invite -spaces.templates.email.activities.childname=cm:activities -spaces.rendition.rendering_actions.childname=app:rendering_actions -spaces.replication.replication_actions.childname=app:replication_actions -spaces.transfers.childname=app:transfers -spaces.transfer_groups.childname=app:transfer_groups -spaces.transfer_temp.childname=app:temp -spaces.inbound_transfer_records.childname=app:inbound_transfer_records -spaces.webscripts.childname=cm:webscripts -spaces.extension_webscripts.childname=cm:extensionwebscripts -spaces.models.childname=app:models -spaces.workflow.definitions.childname=app:workflow_defs -spaces.templates.email.workflowemailnotification.childname=cm:workflownotification -spaces.nodetemplates.childname=app:node_templates -spaces.shared.childname=app:shared -spaces.solr_facets.root.childname=srft:facets -spaces.smartfolders.childname=app:smart_folders -spaces.smartdownloads.childname=app:smart_downloads -spaces.transfer_summary_report.location=/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.inbound_transfer_records.childname} -spaces.quickshare.link_expiry_actions.childname=app:quick_share_link_expiry_actions - - -# ADM VersionStore Configuration -version.store.initialVersion=true -version.store.enableAutoVersioning=true -version.store.enableAutoVersionOnUpdateProps=false -version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore -version.store.version2Store=workspace://version2Store - -# Optional Comparator class name to sort versions. -# Set to: org.alfresco.repo.version.common.VersionLabelComparator -# if upgrading from a version that used unordered sequences in a cluster. -version.store.versionComparatorClass= - -# Folders for storing people -system.system_container.childname=sys:system -system.people_container.childname=sys:people -system.authorities_container.childname=sys:authorities -system.zones_container.childname=sys:zones - -# Folders for storing workflow related info -system.workflow_container.childname=sys:workflow - -# Folder for storing shared remote credentials -system.remote_credentials_container.childname=sys:remote_credentials - -# Folder for storing syncset definitions -system.syncset_definition_container.childname=sys:syncset_definitions - -# Folder for storing download archives -system.downloads_container.childname=sys:downloads - -# Folder for storing IdP's certificate definitions -system.certificate_container.childname=sys:samlcertificate - -# Are user names case sensitive? -user.name.caseSensitive=false -domain.name.caseSensitive=false -domain.separator= - -#Format caption extracted from the XML Schema. -xforms.formatCaption=true - -# ECM content usages/quotas -system.usages.enabled=false -system.usages.clearBatchSize=0 -system.usages.updateBatchSize=50 - -# Repository endpoint - used by Activity Service -repo.remote.endpoint=/service - -# Some authentication mechanisms may need to create people -# in the repository on demand. This enables that feature. -# If disabled an error will be generated for missing -# people. If enabled then a person will be created and -# persisted. -create.missing.people=${server.transaction.allow-writes} - -# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) -home.folder.creation.eager=true -# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) -home.folder.creation.disabled=false - -# Should we consider zero byte content to be the same as no content when firing -# content update policies? Prevents 'premature' firing of inbound content rules -# for some clients such as Mac OS X Finder -policy.content.update.ignoreEmpty=true - -# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. -# This allows connections to JMX both remotely and locally. -# -alfresco.rmi.services.port=50500 -alfresco.rmi.services.external.host=localhost -alfresco.rmi.services.host=0.0.0.0 - -# If the RMI address is in-use, how many retries should be done before aborting -# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' -alfresco.rmi.services.retries=4 -# How long in milliseconds to wait after a failed server socket bind, before retrying -alfresco.rmi.services.retryInterval=250 - -# RMI service ports for the individual services. -# These eight services are available remotely. -# -# Assign individual ports for each service for best performance -# or run several services on the same port, you can even run everything on 50500 if -# running through a firewall. -# -# Specify 0 to use a random unused port. -# -monitor.rmi.service.port=50508 - -# -# enable or disable individual RMI services -# -monitor.rmi.service.enabled=false - - -# Should the Mbean server bind to an existing server. Set to true for most application servers. -# false for WebSphere clusters. -mbean.server.locateExistingServerIfPossible=true - -# Rendition Service 2 -renditionService2.enabled=true - -# Thumbnail Service -system.thumbnail.generate=true - -# when creating doc via CMIS - optionally configure set of renditions names to request async -cmis.create.doc.request.renditions.set= - -# Default thumbnail limits -# When creating thumbnails, only use the first pageLimit pages -system.thumbnail.definition.default.timeoutMs=-1 -system.thumbnail.definition.default.readLimitTimeMs=-1 -system.thumbnail.definition.default.maxSourceSizeKBytes=-1 -system.thumbnail.definition.default.readLimitKBytes=-1 -system.thumbnail.definition.default.pageLimit=-1 -system.thumbnail.definition.default.maxPages=-1 - -# Max mimetype sizes to create thumbnail icons -system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 - -# Configuration for handling of failing thumbnails. -# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. -# -# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails -# for content nodes which have previously failed in their thumbnail attempts. -# These periods are in seconds. -# -# 604800s = 60s * 60m * 24h * 7d = 1 week -system.thumbnail.retryPeriod=60 -system.thumbnail.retryCount=2 -system.thumbnail.quietPeriod=604800 -system.thumbnail.quietPeriodRetriesEnabled=true -system.thumbnail.redeployStaticDefsOnStartup=true - -content.metadata.async.extract.enabled=true -content.metadata.async.embed.enabled=true - -# The default timeout for metadata mapping extracters -content.metadataExtracter.default.timeoutMs=20000 - -# Local transformer urls to T-engines to service transform requests via http. Enabled by default. -localTransform.core-aio.url=http://localhost:8090/ - -# When a local transformer .url is set, this value indicates the amount of time to wait after a connection failure -# before retrying the connection to allow a docker container to (re)start. -localTransform.core-aio.startupRetryPeriodSeconds=60 - -# Property to enable upgrade from 2.1-A -V2.1-A.fixes.to.schema=0 -#V2.1-A.fixes.to.schema=82 - -# The default authentication chain -authentication.chain=alfrescoNtlm1:alfrescoNtlm - -# Do authentication tickets expire or live for ever? -authentication.ticket.ticketsExpire=true - -# If ticketsEpire is true then how they should expire? -# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE -# The default is AFTER_FIXED_TIME -authentication.ticket.expiryMode=AFTER_INACTIVITY - -# If authentication.ticket.ticketsExpire is true and -# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, -# this controls the minimum period for which tickets are valid. -# The default is PT1H for one hour. -authentication.ticket.validDuration=PT1H - -# Use one ticket for all user sessions -# For the pre 4.2 behaviour of one ticket per session set this to false. -authentication.ticket.useSingleTicketPerUser=true - -authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true -authentication.getRemoteUserTimeoutMilliseconds=10000 - -# FTP access -ftp.enabled=false - -# Default root path for protocols -protocols.storeName=${spaces.store} -protocols.rootPath=/${spaces.company_home.childname} - -# OpenCMIS -opencmis.connector.default.store=${spaces.store} -opencmis.connector.default.rootPath=/${spaces.company_home.childname} -opencmis.connector.default.typesDefaultMaxItems=500 -opencmis.connector.default.typesDefaultDepth=-1 -opencmis.connector.default.objectsDefaultMaxItems=10000 -opencmis.connector.default.objectsDefaultDepth=100 -opencmis.connector.default.contentChangesDefaultMaxItems=10000 -opencmis.connector.default.openHttpSession=false -opencmis.activities.enabled=true -opencmis.bulkUpdateProperties.maxItemsSize=1000 -opencmis.bulkUpdateProperties.batchSize=20 -opencmis.bulkUpdateProperties.workerThreads=2 -opencmis.maxContentSizeMB=4096 -opencmis.memoryThresholdKB=4096 - -# URL generation overrides - -# if true, the context path of OpenCMIS generated urls will be set to "opencmis.context.value", otherwise it will be taken from the request url -opencmis.context.override=false -opencmis.context.value= -# if true, the servlet path of OpenCMIS generated urls will be set to "opencmis.servletpath.value", otherwise it will be taken from the request url -opencmis.servletpath.override=false -opencmis.servletpath.value= -opencmis.server.override=false -opencmis.server.value= - -# IMAP -imap.server.enabled=false -imap.server.port=143 -imap.server.attachments.extraction.enabled=true - -# Default IMAP mount points -imap.config.home.store=${spaces.store} -imap.config.home.rootPath=/${spaces.company_home.childname} -imap.config.home.folderPath=${spaces.imap_home.childname} -imap.config.server.mountPoints=AlfrescoIMAP -imap.config.server.mountPoints.default.mountPointName=IMAP -imap.config.server.mountPoints.default.modeName=ARCHIVE -imap.config.server.mountPoints.default.store=${spaces.store} -imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} -imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP -imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED - -#Imap extraction settings -#imap.attachments.mode: -# SEPARATE -- All attachments for each email will be extracted to separate folder. -# COMMON -- All attachments for all emails will be extracted to one folder. -# SAME -- Attachments will be extracted to the same folder where email lies. -imap.attachments.mode=SEPARATE -imap.attachments.folder.store=${spaces.store} -imap.attachments.folder.rootPath=/${spaces.company_home.childname} -imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} - -# Activities Feed - refer to subsystem - -# Feed max ID range to limit maximum number of entries -activities.feed.max.idRange=1000000 -# Feed max size (number of entries) -activities.feed.max.size=200 -# Feed max age (eg. 44640 mins => 31 days) -activities.feed.max.ageMins=44640 - -activities.feed.generator.jsonFormatOnly=true -activities.feed.fetchBatchSize=250 -activities.feedNotifier.batchSize=200 -activities.feedNotifier.numThreads=2 - -# Subsystem unit test values. Will not have any effect on production servers -subsystems.test.beanProp.default.longProperty=123456789123456789 -subsystems.test.beanProp.default.anotherStringProperty=Global Default -subsystems.test.beanProp=inst1,inst2,inst3 -subsystems.test.beanProp.value.inst2.boolProperty=true -subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default -subsystems.test.simpleProp2=true -subsystems.test.simpleProp3=Global Default3 - -# Default Async Action Thread Pool -default.async.action.threadPriority=1 -default.async.action.corePoolSize=8 -default.async.action.maximumPoolSize=20 - -# Deployment Service -deployment.service.numberOfSendingThreads=5 -deployment.service.corePoolSize=2 -deployment.service.maximumPoolSize=3 -deployment.service.threadPriority=5 -# How long to wait in mS before refreshing a target lock - detects shutdown servers -deployment.service.targetLockRefreshTime=60000 -# How long to wait in mS from the last communication before deciding that deployment has failed, possibly -# the destination is no longer available? -deployment.service.targetLockTimeout=3600000 -# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ANSIBLE, ZIP, QUICK_START) -deployment.method=DEFAULT - -#Invitation Service -# Should send emails as part of invitation process. -notification.email.siteinvite=true -# Moderated invite Activiti workflow -site.invite.moderated.workflowId=activiti$activitiInvitationModerated -# Add intneral users Activiti workflow (use activiti$activitiInvitationNominated to revert to requiring accept of invite for internal users) -site.invite.nominated.workflowId=activiti$activitiInvitationNominatedAddDirect -# Add external users Activiti workflow -site.invite.nominatedExternal.workflowId=activiti$activitiInvitationNominated - -# Replication Service -replication.enabled=false - -# Transfer Service -transferservice.receiver.enabled=false -transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging -# -# How long to wait in mS before refreshing a transfer lock - detects shutdown servers -# Default 1 minute. -transferservice.receiver.lockRefreshTime=60000 -# -# How many times to attempt retry the transfer lock -transferservice.receiver.lockRetryCount=3 -# How long to wait, in mS, before retrying the transfer lock -transferservice.receiver.lockRetryWait=100 -# -# How long to wait, in mS, since the last contact with from the client before -# timing out a transfer. Needs to be long enough to cope with network delays and "thinking -# time" for both source and destination. Default 5 minutes. -transferservice.receiver.lockTimeOut=300000 - -# OrphanReaper -orphanReaper.lockRefreshTime=60000 -orphanReaper.lockTimeOut=3600000 - - -# security -security.anyDenyDenies=true -# Whether to post-process denies. Only applies to solr4+ when anyDenyDenies is true. -security.postProcessDenies=false - -# -# Encryption properties -# -# default keystores location -dir.keystore=classpath:alfresco/keystore - -# general encryption parameters -encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator -encryption.keyAlgorithm=AES -encryption.cipherAlgorithm=AES/CBC/PKCS5Padding - -# secret key keystore configuration -encryption.keystore.location=${dir.keystore}/keystore -# configuration via metadata is deprecated -encryption.keystore.keyMetaData.location= -encryption.keystore.provider= -encryption.keystore.type=pkcs12 - -# backup secret key keystore configuration -encryption.keystore.backup.location=${dir.keystore}/backup-keystore -# configuration via metadata is deprecated -encryption.keystore.backup.keyMetaData.location= -encryption.keystore.backup.provider= -encryption.keystore.backup.type=pkcs12 - -# Should encryptable properties be re-encrypted with new encryption keys on botstrap? -encryption.bootstrap.reencrypt=false - -# mac/md5 encryption -encryption.mac.messageTimeout=30000 -encryption.mac.algorithm=HmacSHA1 - -# ssl encryption -encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore -encryption.ssl.keystore.provider= -encryption.ssl.keystore.type=JCEKS -# configuration via metadata is deprecated -encryption.ssl.keystore.keyMetaData.location= -encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore -encryption.ssl.truststore.provider= -encryption.ssl.truststore.type=JCEKS -# configuration via metadata is deprecated -encryption.ssl.truststore.keyMetaData.location= - -# Re-encryptor properties -encryption.reencryptor.chunkSize=100 -encryption.reencryptor.numThreads=2 - -# SOLR connection details (e.g. for JMX) -solr.host=localhost -solr.port=8983 -solr.port.ssl=8984 -solr.solrUser=solr -solr.solrPassword=solr -# none, https -solr.secureComms=https -solr.sharedSecret= -solr.sharedSecret.header=X-Alfresco-Search-Secret -solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY - -solr.max.total.connections=40 -solr.max.host.connections=40 - -# Solr connection timeouts -# solr connect timeout in ms -solr.solrConnectTimeout=5000 - -# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away -solr.solrPingCronExpression=0 0/5 * * * ? * - - -#Default SOLR store mappings mappings -solr.store.mappings=solrMappingAlfresco,solrMappingArchive -solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco -solr.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive -solr.store.mappings.value.solrMappingArchive.protocol=archive -solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore - -#Default SOLR 4 store mappings mappings -solr4.store.mappings=solrMappingAlfresco,solrMappingArchive -solr4.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr4.store.mappings.value.solrMappingAlfresco.baseUrl=/solr4/alfresco -solr4.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr4.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr4.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr4.store.mappings.value.solrMappingArchive.baseUrl=/solr4/archive -solr4.store.mappings.value.solrMappingArchive.protocol=archive -solr4.store.mappings.value.solrMappingArchive.identifier=SpacesStore - -#Default SOLR 6 store mappings mappings -solr6.store.mappings=solrMappingAlfresco,solrMappingArchive,solrMappingHistory -solr6.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco -solr6.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr6.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr6.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive -solr6.store.mappings.value.solrMappingArchive.protocol=archive -solr6.store.mappings.value.solrMappingArchive.identifier=SpacesStore -solr6.store.mappings.value.solrMappingHistory.httpClientFactory=solrHttpClientFactory -solr6.store.mappings.value.solrMappingHistory.baseUrl=/solr/history -solr6.store.mappings.value.solrMappingHistory.protocol=workspace -solr6.store.mappings.value.solrMappingHistory.identifier=history - -# -# URL Shortening Properties -# -urlshortening.bitly.username=brianalfresco -urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f -urlshortening.bitly.url.length=20 - -# -# Bulk Filesystem Importer -# - -# The number of threads to employ in a batch import -bulkImport.batch.numThreads=4 - -# The size of a batch in a batch import i.e. the number of files to import in a -# transaction/thread -bulkImport.batch.batchSize=20 - - -# -# Caching Content Store -# -system.content.caching.cacheOnInbound=true -system.content.caching.maxDeleteWatchCount=1 -# Clean up every day at 3 am -system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? -system.content.caching.minFileAgeMillis=60000 -system.content.caching.maxUsageMB=4096 -# maxFileSizeMB - 0 means no max file size. -system.content.caching.maxFileSizeMB=0 -# When the CachingContentStore is about to write a cache file but the disk usage is in excess of panicThresholdPct -# (default 90%) then the cache file is not written and the cleaner is started (if not already running) in a new thread. -system.content.caching.panicThresholdPct=90 -# When a cache file has been written that results in cleanThresholdPct (default 80%) of maxUsageBytes -# being exceeded then the cached content cleaner is invoked (if not already running) in a new thread. -system.content.caching.cleanThresholdPct=80 -# An aggressive cleaner is run till the targetUsagePct (default 70%) of maxUsageBytes is achieved -system.content.caching.targetUsagePct=70 -# Threshold in seconds indicating a minimal gap between normal cleanup starts -system.content.caching.normalCleanThresholdSec=0 - -mybatis.useLocalCaches=false - -fileFolderService.checkHidden.enabled=true - - -ticket.cleanup.cronExpression=0 0 * * * ? - -# -# Download Service Cleanup -# -download.cleaner.startDelayMilliseconds=3600000 -# 1 hour -download.cleaner.repeatIntervalMilliseconds=3600000 -download.cleaner.maxAgeMins=60 -# -1 or 0 for not using batches -download.cleaner.batchSize=1000 - -# you could set this to false for new installations greater then ACS 6.2 -# see MNT-20212 -download.cleaner.cleanAllSysDownloadFolders=true - -# -# Download Service Limits, in bytes -# -download.maxContentSize=2152852358 - -# Max size of view trashcan files -# -trashcan.MaxSize=1000 - -# -# Use bridge tables for caching authority evaluation. -# -authority.useBridgeTable=true - -# Limit the number of results from findAuthority query -authority.findAuthorityLimit=10000 - -# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden -system.quickshare.enabled=true -system.quickshare.email.from.default=noreply@alfresco.com -# By default the difference between the quick share expiry date and the current time must be at least 1 day (24 hours). -# However, this can be changed to at least 1 hour or 1 minute for testing purposes. For example, -# setting the value to MINUTES, means the service will calculate the difference between NOW and the given expiry date -# in terms of minutes and checks for the difference to be greater than 1 minute. -# DAYS | HOURS | MINUTES -system.quickshare.expiry_date.enforce.minimum.period=DAYS - -# Oubound Mail -mail.service.corePoolSize=8 -mail.service.maximumPoolSize=20 - -nodes.bulkLoad.cachingThreshold=10 - -# Multi-Tenancy - -# if "dir.contentstore.tenants" is set then -# tenants are not co-mingled and all content roots will appear below this container (in sub-folder) -# and when creating a tenant the "contentRootPath" (root content store directory for a given tenant) will be ignored -dir.contentstore.tenants= - -# Gateway Authentication -# gateway authentication is disabled if empty host is specified -alfresco.authentication.gateway.host= -alfresco.authentication.gateway.protocol=https -alfresco.authentication.gateway.port=443 -alfresco.authentication.gateway.outboundHeaders=Authorization,key -alfresco.authentication.gateway.inboundHeaders=X-Alfresco-Authenticator-Key,X-Alfresco-Remote-User -alfresco.authentication.gateway.prefixUrl=/publicapi -alfresco.authentication.gateway.bufferSize=2048 -alfresco.authentication.gateway.connectTimeout=10000 -alfresco.authentication.gateway.readTimeout=120000 -alfresco.authentication.gateway.httpTcpNodelay=true -alfresco.authentication.gateway.httpConnectionStalecheck=true - -# webscripts config -webscripts.encryptTempFiles=false -webscripts.tempDirectoryName=Alfresco-WebScripts -# 4mb -webscripts.memoryThreshold=4194304 -# 4gb -webscripts.setMaxContentSize=5368709120 - -# Property to enable index upgrade for metadata query (MDQ) -# -# The indexes are not added unless this value is changed -# Adding each the supporting indexes may take several hours depending on the size of the database. -# The required indexes may be added in stages. -# See: classpath:alfresco/dbscripts/upgrade/4.2/${db.script.dialect}/metadata-query-indexes.sql -# See: classpath:alfresco/dbscripts/upgrade/5.1/${db.script.dialect}/metadata-query-indexes-2.sql -system.metadata-query-indexes.ignored=true -system.metadata-query-indexes-more.ignored=true - -# -# Do we defer running the shared folder patch? -# -system.patch.sharedFolder.deferred=false -# Default value is run new years day 2030 i.e. not run. -system.patch.sharedFolder.cronExpression=0 0 0 ? 1 1 2030 - -# -# Default values for deferring the running of the addUnmovableAspect patch -# -system.patch.addUnmovableAspect.deferred=false -system.patch.addUnmovableAspect.cronExpression=0 0 0 ? 1 1 2030 - -# Property to enable removal of all JBPM related data from the database -# -# The tables are not removed from the databasen unless explicitly requested by setting this property to false. -# See: classpath:alfresco/dbscripts/upgrade/5.2/${db.script.dialect}/remove-jbpm-tables-from-db.sql -system.remove-jbpm-tables-from-db.ignored=true - -# -# Use a canned query when requested to search for people if " [hint:useCQ]" is provided in search term -# -people.search.honor.hint.useCQ=true - -# Delays cron jobs after bootstrap to allow server to fully come up before jobs start -system.cronJob.startDelayMilliseconds=60000 - -# Schedule for reading mimetype config definitions dynamically. Initially checks every 10 seconds and then switches to -# every hour after the configuration is read successfully. If there is a error later reading the config, the -# checks return to every 10 seconds. -mimetype.config.cronExpression=0 30 0/1 * * ? -mimetype.config.initialAndOnError.cronExpression=0/10 * * * * ? - -# Optional property to specify an external file or directory that will be read for mimetype definitions from YAML -# files (possibly added to a volume via k8 ConfigMaps). -mimetype.config.dir=shared/classes/alfresco/extension/mimetypes - -# Schedule for reading rendition config definitions dynamically. Initially checks every 10 seconds and then switches to -# every hour after the configuration is read successfully. If there is a error later reading the config, the -# checks return to every 10 seconds. -rendition.config.cronExpression=2 30 0/1 * * ? -rendition.config.initialAndOnError.cronExpression=0/10 * * * * ? - -# Optional property to specify an external file or directory that will be read for rendition definitions from YAML -# files (possibly added to a volume via k8 ConfigMaps). -rendition.config.dir=shared/classes/alfresco/extension/transform/renditions - -# Optional property to specify an external file or directory that will be read for transformer json config. -local.transform.pipeline.config.dir=shared/classes/alfresco/extension/transform/pipelines - -# Used to disable transforms locally. -local.transform.service.enabled=true - -# Schedule for reading local transform config, so that T-Engines and local pipeline config is dynamically -# picked up, or reintegrated after an outage. Initially checks every 10 seconds and then switches to every hour -# after the configuration is read successfully. If there is a error later reading the config, the checks return to -# every 10 seconds. -local.transform.service.cronExpression=4 30 0/1 * * ? -local.transform.service.initialAndOnError.cronExpression=0/10 * * * * ? - -# -# Check that the declared mimetype (of the Node) is the same as the derived -# mimetype of the content (via Tika) before a transformation takes place. -# Only files in the repository (not intermediate files in a transformer -# pipeline) are checked. This property provides a trade off between a -# security check and a relatively expensive (Tika) operation. -# -# There are a few issues with the Tika mimetype detection. So that transformations -# still take place where the detected mimetype is not the same as the declared mimetype, -# another property (transformer.strict.mimetype.check.whitelist.mimetypes) contains pairs -# of declared and detected mimetypes that should be allowed. This parameter value is a -# sequence of ; separated pairs. The declared and derived mimetypes are also ; separated. -# -transformer.strict.mimetype.check=true - -# A white list of declared and detected mimetypes, that don't match, but should still be transformed. -transformer.strict.mimetype.check.whitelist.mimetypes=application/eps;application/postscript;application/illustrator;application/pdf;application/x-tar;application/x-gtar;application/acp;application/zip;application/vnd.stardivision.math;application/x-tika-msoffice - -# -# Enable transformation retrying if the file has MIME type differ than file extension. -# Ignored if transformer.strict.mimetype.check is true as these transformations -# will not take place. -# -content.transformer.retryOn.different.mimetype=true - -# Debug and Log buffer sizes -transformer.debug.entries=0 -transformer.log.entries=50 - -# -# Lock timeout configuration -# -system.lockTryTimeout=100 -system.lockTryTimeout.DictionaryDAOImpl=10000 -system.lockTryTimeout.MessageServiceImpl=${system.lockTryTimeout} -system.lockTryTimeout.PolicyComponentImpl=${system.lockTryTimeout} - - -# Scheduled job to clean up unused properties from the alf_prop_xxx tables. -# Default setting of "0 0 3 ? * SAT" is to run every Saturday at 3am. -attributes.propcleaner.cronExpression=0 0 3 ? * SAT - -# Control Alfresco JMX connectivity -alfresco.jmx.connector.enabled=false - -# Dissallow Attribute Service Entries with "Serializable" objects in key Segments -# Please, see MNT-11895 for details. -system.propval.uniquenessCheck.enabled=true - -# Requests for ephemeral (in-memory) locks with expiry times (in seconds) greater -# than this value will result in persistent locks being created instead. By default -# this value is equal to the maximum allowed expiry for ephemeral locks, therefore -# this feature is disabled by default. Setting this to -1 would mean that ALL -# requests for ephemeral locks would result in persistent locks being created. -alfresco.ephemeralLock.expiryThresh=172800 - -# SurfConfigFolder Patch -# -# Do we defer running the surf-config folder patch? -# -system.patch.surfConfigFolder.deferred=false -# Default value. i.e. never run. It can be triggered using JMX -system.patch.surfConfigFolder.cronExpression=* * * * * ? 2099 - -# -# Solr Facets Config Properties -# -solr_facets.root.path=/app:company_home/app:dictionary -solr_facets.root=${solr_facets.root.path}/${spaces.solr_facets.root.childname} -solr_facets.inheritanceHierarchy=default,custom - -models.enforceTenantInNamespace=false - -# Allowed protocols for links -links.protocosl.white.list=http,https,ftp,mailto - -# Fixed ACLs -# Required for fixing MNT-15368 - Time Consumed for Updating Folder Permission -# ADMAccessControlListDAO.setFixedAcls called on a large folder hierarchy will take a long time for its execution. -# For this reason now method can also be called asynchronously if transaction reaches system.fixedACLs.maxTransactionTime. -# In this case setFixedAcls method recursion will be stopped and unfinished nodes will be marked with ASPECT_PENDING_FIX_ACL. -# Pending nodes will be processed by FixedAclUpdater, programmatically called but also configured as a scheduled job. -system.fixedACLs.maxTransactionTime=10000 -# fixedACLsUpdater - lock time to live -system.fixedACLsUpdater.lockTTL=10000 -# fixedACLsUpdater - maximum number of nodes to process per execution -system.fixedACLsUpdater.maxItemBatchSize=100 -# fixedACLsUpdater - the number of threads to use -system.fixedACLsUpdater.numThreads=4 -# fixedACLsUpdater - Force shared ACL to propagate through children even if there is an unexpected ACL -system.fixedACLsUpdater.forceSharedACL=false -# fixedACLsUpdater cron expression - fire at midnight every day -system.fixedACLsUpdater.cronExpression=0 0 0 * * ? - -cmis.disable.hidden.leading.period.files=false - -#Smart Folders Config Properties -smart.folders.enabled=false - -#Smart reference config -smart.reference.classpath.hash=${smart.folders.config.vanilla.processor.classpath}->1,${smart.folders.config.system.templates.classpath}->2 - -#Smart store config - -#Company home relative download associations of smart entries -smart.download.associations.folder=${spaces.dictionary.childname}/${spaces.smartdownloads.childname} - -#Generic virtualization methods config - -#Vanilla JSON templates javascript processor classpath. A java script processor used to -#covert JSON templates to internal smart folder definitions. - -smart.folders.config.vanilla.processor.classpath=/org/alfresco/repo/virtual/node/vanilla.js - -#System virtualization method config - -#System virtualization method aspect. -smart.folders.config.system.aspect=smf:systemConfigSmartFolder -#System virtualization method aspect defined template location property. -smart.folders.config.system.aspect.template.location.property=smf:system-template-location -#Classpath to be explored for *.json entries defining system templates. -smart.folders.config.system.templates.classpath=/org/alfresco/repo/virtual/node -#A company home relative name or qname path location of repository system templates. -smart.folders.config.system.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} -#Content sub type of repository system templates. -smart.folders.config.system.templates.template.type=smf:smartFolderTemplate - -#Custom virtualization method config - -#Custom virtualization method aspect. -smart.folders.config.custom.aspect=smf:customConfigSmartFolder -#Custom virtualization method aspect template content association. -smart.folders.config.custom.aspect.template.association=smf:custom-template-association - - -#Type virtualization method config - -#A company home relative name or qname path location of the type mapped templates. -smart.folders.config.type.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} -#Type and aspect qname regular expression filter. -smart.folders.config.type.templates.qname.filter=none - -# Preferred password encoding, md4, sha256, bcrypt10 -system.preferred.password.encoding=md4 - -# Upgrade Password Hash Job -system.upgradePasswordHash.jobBatchSize=100 -system.upgradePasswordHash.jobQueryRange=10000 -system.upgradePasswordHash.jobThreadCount=4 -system.upgradePasswordHash.jobCronExpression=* * * * * ? 2099 - -system.api.discovery.enabled=true - -# Maximum query size for category/tag fetch when not explicitly set by paging parameters -category.queryFetchSize=5000 - -# Brute force protection -authentication.protection.enabled=true -authentication.protection.limit=10 -authentication.protection.periodSeconds=6 - -system.email.sender.default=noreply@alfresco.com -# reset password workflow will expire in an hour -system.reset-password.endTimer=PT1H -system.reset-password.sendEmailAsynchronously=true - -# HeartBeat -heartbeat.target.url= -heartbeat.enabled=true - -# CSRF filter overrides -csrf.filter.enabled=true -csrf.filter.referer= -csrf.filter.referer.always=false -csrf.filter.origin= -csrf.filter.origin.always=false - -# CORS settings -cors.enabled=false -cors.allowed.origins= -cors.allowed.methods=GET,POST,HEAD,OPTIONS,PUT,DELETE -cors.allowed.headers=Authorization,Content-Type,Cache-Control,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,X-CSRF-Token -cors.exposed.headers=Access-Control-Allow-Origin,Access-Control-Allow-Credentials -cors.support.credentials=true -cors.preflight.maxage=10 - -# Alfresco Rest Api-Explorer -api-explorer.url= - -# Events subsystem -events.subsystem.autoStart=false -# Messaging subsystem -messaging.subsystem.autoStart=true - - -# Raw events -acs.repo.rendition.events.endpoint=jms:acs-repo-rendition-events?jmsMessageType=Text - -# Transform request events -acs.repo.transform.request.endpoint=jms:acs-repo-transform-request?jmsMessageType=Text - -# If enabled doesn't allow to set content properties via NodeService -contentPropertyRestrictions.enabled=true -contentPropertyRestrictions.whitelist= - -# Repo events2 -# Type and aspect filters which should be excluded -# Note: System folders node types are added by default -repo.event2.filter.nodeTypes=sys:*, fm:*, cm:thumbnail, cm:failedThumbnail, cm:rating, rma:rmsite include_subtypes -repo.event2.filter.nodeAspects=sys:* -repo.event2.filter.childAssocTypes=rn:rendition -# Comma separated list of users which should be excluded -# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting -repo.event2.filter.users= -# Topic name -repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2 -# Thread pool for async enqueue of repo events -repo.event2.queue.enqueueThreadPool.priority=1 -repo.event2.queue.enqueueThreadPool.coreSize=8 -repo.event2.queue.enqueueThreadPool.maximumSize=10 -# Thread pool for async dequeue and delivery of repo events -repo.event2.queue.dequeueThreadPool.priority=1 -repo.event2.queue.dequeueThreadPool.coreSize=1 -repo.event2.queue.dequeueThreadPool.maximumSize=1 - - -# MNT-21083 -# --DELETE_NOT_EXISTS - default settings -system.delete_not_exists.batchsize=100000 -system.delete_not_exists.delete_batchsize=1000 -system.delete_not_exists.read_only=false -system.delete_not_exists.timeout_seconds=-1 -system.prop_table_cleaner.algorithm=V2 - -# Configure the expiration time of the direct access url. This is the length of time in seconds that the link is valid for. -# Note: It is up to the actual ContentStore implementation if it can fulfil this request or not. -alfresco.content.directAccessUrl.lifetimeInSec=300 - -# Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories. -system.new-node-transaction-indexes.ignored=true - -# Allows the configuration of maximum limits of the temp files to be deleted or the maximum time allowed to run for the job -system.tempFileCleaner.maxFilesToDelete= -system.tempFileCleaner.maxTimeToRun= - -# Property to long running migration to remove alf_server in v7+ patch.db-V7.1.0-remove-alf_server-table -system.remove-alf_server-table-from-db.ignored=true +# Repository configuration + +repository.name=Main Repository + +# Schema number +version.schema=15001 + +# Directory configuration + +dir.root=./alf_data + +dir.contentstore=${dir.root}/contentstore +dir.contentstore.deleted=${dir.root}/contentstore.deleted +dir.contentstore.bucketsPerMinute=0 + +# ContentStore subsystem: default choice +filecontentstore.subsystem.name=unencryptedContentStore + +# The location of cached content +dir.cachedcontent=${dir.root}/cachedcontent + +# The value for the maximum permitted size in bytes of all content. +# No value (or a negative long) will be taken to mean that no limit should be applied. +# See content-services-context.xml +system.content.maximumFileSizeLimit= + +# +# The server mode. Set value in alfresco-global.properties +# UNKNOWN | TEST | BACKUP | PRODUCTION +# +system.serverMode=UNKNOWN + +# The location for lucene index files +dir.indexes=${dir.root}/lucene-indexes + +# The location for index backups +dir.indexes.backup=${dir.root}/backup-lucene-indexes + +# The location for lucene index locks +dir.indexes.lock=${dir.indexes}/locks + +#Directory to find external license +dir.license.external=. +# Spring resource location of external license files +location.license.external=file://${dir.license.external}/*.lic +# Spring resource location of embedded license files +location.license.embedded=/WEB-INF/alfresco/license/*.lic +# Spring resource location of license files on shared classpath +location.license.shared=classpath*:/alfresco/extension/license/*.lic + +# WebDAV initialization properties +system.webdav.servlet.enabled=true +system.webdav.url.path.prefix= +system.webdav.storeName=${protocols.storeName} +system.webdav.rootPath=${protocols.rootPath} +# File name patterns that trigger rename shuffle detection +# pattern is used by move - tested against full path after it has been lower cased. +system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*atmp[0-9]+$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$)|(.*\\.sb\\-\\w{8}\\-\\w{6}$) +system.webdav.activities.enabled=false + + +system.workflow.jbpm.comment.property.max.length=-1 +system.workflow.comment.property.max.length=4000 + +#Determines if Activiti definitions are visible +system.workflow.engine.activiti.definitions.visible=true + + +# Determines if the Activiti engine is enabled +system.workflow.engine.activiti.enabled=true +system.workflow.engine.activiti.idblocksize=100 +system.workflow.engine.activiti.taskvariableslimit=20000 + +# Determines if the workflows that are deployed to the activiti engine should +# be deployed in the tenant-context of the thread IF the tenant-service is enabled +# If set to false, all workflows deployed will be shared among tenants. Recommended +# setting is true unless there is a good reason to not allow deploy tenant-specific +# worklfows when a MT-environment is set up. +system.workflow.deployWorkflowsInTenant=true +#Determines if historic process instance are retained in case of canceling a process instance +system.workflow.engine.activiti.retentionHistoricProcessInstance=false + +# The maximum number of groups to check for pooled tasks. For performance +# reasons, this is limited to 500 by default. +system.workflow.maxAuthoritiesForPooledTasks=500 + +# The maximum number of pooled tasks to return in a query. It may be necessary +# to limit this depending on UI limitations. +system.workflow.maxPooledTasks=-1 + +# The maximum number of reviewers for "Group Review and Approve" workflow. +# Use '0' for unlimited. +system.workflow.maxGroupReviewers=0 + +index.subsystem.name=noindex + +# ######################################### # +# Index Tracking Configuration # +# ######################################### # +# +# Index tracking information of a certain age is cleaned out by a scheduled job. +# Any clustered system that has been offline for longer than this period will need to be seeded +# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. +# Use -1 to disable purging. This can be switched on at any stage. +index.tracking.minRecordPurgeAgeDays=30 +# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size +# of the chunk (in ms). Default is a couple of hours. +index.tracking.purgeSize=7200000 + +# Change the failure behaviour of the configuration checker +system.bootstrap.config_check.strict=true + + +# +# How long should shutdown wait to complete normally before +# taking stronger action and calling System.exit() +# in ms, 10,000 is 10 seconds +# +shutdown.backstop.timeout=10000 +shutdown.backstop.enabled=false + +# Server Single User Mode +# note: +# only allow named user (note: if blank or not set then will allow all users) +# assuming maxusers is not set to 0 +#server.singleuseronly.name=admin + +# Server Max Users - limit number of users with non-expired tickets +# note: +# -1 allows any number of users, assuming not in single-user mode +# 0 prevents further logins, including the ability to enter single-user mode +server.maxusers=-1 + +# +# Disable all shared caches (mutable and immutable) +# These properties are used for diagnostic purposes +system.cache.disableMutableSharedCaches=false +system.cache.disableImmutableSharedCaches=false + +# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) +system.cache.parentAssocs.maxSize=130000 + +# The average number of parents expected per cache entry. This parameter is multiplied by the above +# value to compute a limit on the total number of cached parents, which will be proportional to the +# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive +# memory usage. +system.cache.parentAssocs.limitFactor=8 + +# +# Properties to limit resources spent on individual searches +# +# The maximum time spent pruning results +system.acl.maxPermissionCheckTimeMillis=10000 +# The maximum number of search results to perform permission checks against +system.acl.maxPermissionChecks=1000 +system.acl.maxPermissionCheckEnabled=false + +# The maximum number of filefolder list results +system.filefolderservice.defaultListMaxResults=5000 +# DEPRECATED: Use 'system.auditableData.preserve' +system.preserve.modificationData=false +# The default to preserve all cm:auditable data on a node when the process is not directly driven by a user action +system.auditableData.preserve=${system.preserve.modificationData} +# Specific control of how the FileFolderService treats cm:auditable data when performing moves +system.auditableData.FileFolderService=${system.auditableData.preserve} +# Specific control of whether ACL changes on a node trigger the cm:auditable aspect +system.auditableData.ACLs=${system.auditableData.preserve} + +# Properties to control read permission evaluation for acegi +system.readpermissions.optimise=true +system.readpermissions.bulkfetchsize=1000 + +# +# Manually control how the system handles maximum string lengths. +# Any zero or negative value is ignored. +# Only change this after consulting support or reading the appropriate Javadocs for +# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2. +# Before database migration, the string value storage may need to be adjusted using the scheduled job +system.maximumStringLength=-1 +system.maximumStringLength.jobCronExpression=* * * * * ? 2099 +system.maximumStringLength.jobQueryRange=10000 +system.maximumStringLength.jobThreadCount=4 + +# +# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation +# - hibernate works as is up to this size +# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance +# events may not group if there are post action listener registered (this is not the case with the default distribution) +system.hibernateMaxExecutions=20000 + +# +# Determine if modification timestamp propagation from child to parent nodes is respected or not. +# Even if 'true', the functionality is only supported for child associations that declare the +# 'propagateTimestamps' element in the dictionary definition. +system.enableTimestampPropagation=true + +# +# Enable system model integrity checking. +# WARNING: Changing this is unsupported; bugs may corrupt data +system.integrity.enabled=true +# Do integrity violations fail transactions +# WARNING: Changing this is unsupported; bugs may corrupt data +system.integrity.failOnViolation=true +# The number of errors to report when violations are detected +system.integrity.maxErrorsPerTransaction=5 +# Add call stacks to integrity events so that errors are logged with possible causes +# WARNING: This is expensive and should only be switched on for diagnostic purposes +system.integrity.trace=false + +# +# Decide if content should be removed from the system immediately after being orphaned. +# Do not change this unless you have examined the impact it has on your backup procedures. +system.content.eagerOrphanCleanup=false +# The number of days to keep orphaned content in the content stores. +# This has no effect on the 'deleted' content stores, which are not automatically emptied. +system.content.orphanProtectDays=14 +# The action to take when a store or stores fails to delete orphaned content +# IGNORE: Just log a warning. The binary remains and the record is expunged +# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. +system.content.deletionFailureAction=IGNORE +# The CRON expression to trigger the deletion of resources associated with orphaned content. +system.content.orphanCleanup.cronExpression=0 0 4 * * ? +# The batch size user by the content store cleaner +system.content.cleanerBatchSize=1000 + +# The CRON expression to trigger the cleanup of deleted nodes and dangling transactions that are old enough +system.nodeServiceCleanup.cronExpression=0 0 21 * * ? + +# When transforming archive files (.zip etc) into text representations (such as +# for full text indexing), should the files within the archive be processed too? +# If enabled, transformation takes longer, but searches of the files find more. +transformer.Archive.includeContents=false + +# Database configuration +db.schema.name= +db.schema.stopAfterSchemaBootstrap=false +db.schema.update=true +db.schema.update.lockRetryCount=24 +db.schema.update.lockRetryWaitSeconds=5 +db.driver=org.gjt.mm.mysql.Driver +db.name=alfresco +db.url=jdbc:mysql:///${db.name} +db.username=alfresco +db.password=alfresco +db.pool.initial=10 +db.pool.max=275 +db.txn.isolation=-1 +db.pool.statements.enable=true +db.pool.statements.max=40 +db.pool.min=10 +db.pool.idle=10 +db.pool.wait.max=5000 + +db.pool.validate.query= +db.pool.evict.interval=600000 +db.pool.evict.idle.min=1800000 +# +# note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) +# and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) +# +# * The number of objects to examine during each run of the idle object evictor thread (if any). +# * When a negative value is supplied, ceil({@link #getNumIdle})/abs({@link #getNumTestsPerEvictionRun}) +# * tests will be run. I.e., when the value is -n, roughly one nth of the +# * idle objects will be tested per run. +# +db.pool.evict.num.tests=-1 + +db.pool.evict.validate=false +db.pool.validate.borrow=true +db.pool.validate.return=false + +db.pool.abandoned.detect=false +db.pool.abandoned.time=300 +# +# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) +# and also requires db.pool.abandoned.detect=true (removeAbandoned) +# +db.pool.abandoned.log=false + + +# Audit configuration +audit.enabled=true +audit.tagging.enabled=true +audit.alfresco-access.enabled=false +audit.alfresco-access.sub-actions.enabled=false +audit.cmischangelog.enabled=false +audit.dod5015.enabled=false +# Setting this flag to true will force startup failure when invalid audit configurations are detected +audit.config.strict=false +# Audit map filter for AccessAuditor - restricts recorded events to user driven events +audit.filter.alfresco-access.default.enabled=false +audit.filter.alfresco-access.transaction.user=~System;~null;.* +audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site +audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* + + +# System Configuration +system.store=system://system +system.descriptor.childname=sys:descriptor +system.descriptor.current.childname=sys:descriptor-current + +# User config +alfresco_user_store.store=user://alfrescoUserStore +alfresco_user_store.system_container.childname=sys:system +alfresco_user_store.user_container.childname=sys:people + +# note: default admin username - should not be changed after installation +alfresco_user_store.adminusername=admin + +# Initial password - editing this will not have any effect once the repository is installed +alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 + +# note: default guest username - should not be changed after installation +alfresco_user_store.guestusername=guest + +# Used to move home folders to a new location +home_folder_provider_synchronizer.enabled=false +home_folder_provider_synchronizer.override_provider= +home_folder_provider_synchronizer.keep_empty_parents=false + +# Spaces Archive Configuration +spaces.archive.store=archive://SpacesStore + +# Spaces Configuration +spaces.store=workspace://SpacesStore +spaces.company_home.childname=app:company_home +spaces.guest_home.childname=app:guest_home +spaces.dictionary.childname=app:dictionary +spaces.templates.childname=app:space_templates +spaces.imap_attachments.childname=cm:Imap Attachments +spaces.imap_home.childname=cm:Imap Home +spaces.imapConfig.childname=app:imap_configs +spaces.imap_templates.childname=app:imap_templates +spaces.scheduled_actions.childname=cm:Scheduled Actions +spaces.emailActions.childname=app:email_actions +spaces.searchAction.childname=cm:search +spaces.templates.content.childname=app:content_templates +spaces.templates.email.childname=app:email_templates +spaces.templates.email.invite1.childname=app:invite_email_templates +spaces.templates.email.notify.childname=app:notify_email_templates +spaces.templates.email.following.childname=app:following +spaces.templates.rss.childname=app:rss_templates +spaces.savedsearches.childname=app:saved_searches +spaces.scripts.childname=app:scripts +spaces.content_forms.childname=app:forms +spaces.user_homes.childname=app:user_homes +spaces.user_homes.regex.key=userName +spaces.user_homes.regex.pattern= +spaces.user_homes.regex.group_order= +spaces.sites.childname=st:sites +spaces.templates.email.invite.childname=cm:invite +spaces.templates.email.activities.childname=cm:activities +spaces.rendition.rendering_actions.childname=app:rendering_actions +spaces.replication.replication_actions.childname=app:replication_actions +spaces.transfers.childname=app:transfers +spaces.transfer_groups.childname=app:transfer_groups +spaces.transfer_temp.childname=app:temp +spaces.inbound_transfer_records.childname=app:inbound_transfer_records +spaces.webscripts.childname=cm:webscripts +spaces.extension_webscripts.childname=cm:extensionwebscripts +spaces.models.childname=app:models +spaces.workflow.definitions.childname=app:workflow_defs +spaces.templates.email.workflowemailnotification.childname=cm:workflownotification +spaces.nodetemplates.childname=app:node_templates +spaces.shared.childname=app:shared +spaces.solr_facets.root.childname=srft:facets +spaces.smartfolders.childname=app:smart_folders +spaces.smartdownloads.childname=app:smart_downloads +spaces.transfer_summary_report.location=/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.inbound_transfer_records.childname} +spaces.quickshare.link_expiry_actions.childname=app:quick_share_link_expiry_actions + + +# ADM VersionStore Configuration +version.store.initialVersion=true +version.store.enableAutoVersioning=true +version.store.enableAutoVersionOnUpdateProps=false +version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore +version.store.version2Store=workspace://version2Store + +# Optional Comparator class name to sort versions. +# Set to: org.alfresco.repo.version.common.VersionLabelComparator +# if upgrading from a version that used unordered sequences in a cluster. +version.store.versionComparatorClass= + +# Folders for storing people +system.system_container.childname=sys:system +system.people_container.childname=sys:people +system.authorities_container.childname=sys:authorities +system.zones_container.childname=sys:zones + +# Folders for storing workflow related info +system.workflow_container.childname=sys:workflow + +# Folder for storing shared remote credentials +system.remote_credentials_container.childname=sys:remote_credentials + +# Folder for storing syncset definitions +system.syncset_definition_container.childname=sys:syncset_definitions + +# Folder for storing download archives +system.downloads_container.childname=sys:downloads + +# Folder for storing IdP's certificate definitions +system.certificate_container.childname=sys:samlcertificate + +# Are user names case sensitive? +user.name.caseSensitive=false +domain.name.caseSensitive=false +domain.separator= + +#Format caption extracted from the XML Schema. +xforms.formatCaption=true + +# ECM content usages/quotas +system.usages.enabled=false +system.usages.clearBatchSize=0 +system.usages.updateBatchSize=50 + +# Repository endpoint - used by Activity Service +repo.remote.endpoint=/service + +# Some authentication mechanisms may need to create people +# in the repository on demand. This enables that feature. +# If disabled an error will be generated for missing +# people. If enabled then a person will be created and +# persisted. +create.missing.people=${server.transaction.allow-writes} + +# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) +home.folder.creation.eager=true +# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) +home.folder.creation.disabled=false + +# Should we consider zero byte content to be the same as no content when firing +# content update policies? Prevents 'premature' firing of inbound content rules +# for some clients such as Mac OS X Finder +policy.content.update.ignoreEmpty=true + +# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. +# This allows connections to JMX both remotely and locally. +# +alfresco.rmi.services.port=50500 +alfresco.rmi.services.external.host=localhost +alfresco.rmi.services.host=0.0.0.0 + +# If the RMI address is in-use, how many retries should be done before aborting +# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' +alfresco.rmi.services.retries=4 +# How long in milliseconds to wait after a failed server socket bind, before retrying +alfresco.rmi.services.retryInterval=250 + +# RMI service ports for the individual services. +# These eight services are available remotely. +# +# Assign individual ports for each service for best performance +# or run several services on the same port, you can even run everything on 50500 if +# running through a firewall. +# +# Specify 0 to use a random unused port. +# +monitor.rmi.service.port=50508 + +# +# enable or disable individual RMI services +# +monitor.rmi.service.enabled=false + + +# Should the Mbean server bind to an existing server. Set to true for most application servers. +# false for WebSphere clusters. +mbean.server.locateExistingServerIfPossible=true + +# Rendition Service 2 +renditionService2.enabled=true + +# Thumbnail Service +system.thumbnail.generate=true + +# when creating doc via CMIS - optionally configure set of renditions names to request async +cmis.create.doc.request.renditions.set= + +# Default thumbnail limits +# When creating thumbnails, only use the first pageLimit pages +system.thumbnail.definition.default.timeoutMs=-1 +system.thumbnail.definition.default.readLimitTimeMs=-1 +system.thumbnail.definition.default.maxSourceSizeKBytes=-1 +system.thumbnail.definition.default.readLimitKBytes=-1 +system.thumbnail.definition.default.pageLimit=-1 +system.thumbnail.definition.default.maxPages=-1 + +# Max mimetype sizes to create thumbnail icons +system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 + +# Configuration for handling of failing thumbnails. +# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. +# +# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails +# for content nodes which have previously failed in their thumbnail attempts. +# These periods are in seconds. +# +# 604800s = 60s * 60m * 24h * 7d = 1 week +system.thumbnail.retryPeriod=60 +system.thumbnail.retryCount=2 +system.thumbnail.quietPeriod=604800 +system.thumbnail.quietPeriodRetriesEnabled=true +system.thumbnail.redeployStaticDefsOnStartup=true + +content.metadata.async.extract.enabled=true +content.metadata.async.embed.enabled=true + +# The default timeout for metadata mapping extracters +content.metadataExtracter.default.timeoutMs=20000 + +# Local transformer urls to T-engines to service transform requests via http. Enabled by default. +localTransform.core-aio.url=http://localhost:8090/ + +# When a local transformer .url is set, this value indicates the amount of time to wait after a connection failure +# before retrying the connection to allow a docker container to (re)start. +localTransform.core-aio.startupRetryPeriodSeconds=60 + +# Property to enable upgrade from 2.1-A +V2.1-A.fixes.to.schema=0 +#V2.1-A.fixes.to.schema=82 + +# The default authentication chain +authentication.chain=alfrescoNtlm1:alfrescoNtlm + +# Do authentication tickets expire or live for ever? +authentication.ticket.ticketsExpire=true + +# If ticketsEpire is true then how they should expire? +# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE +# The default is AFTER_FIXED_TIME +authentication.ticket.expiryMode=AFTER_INACTIVITY + +# If authentication.ticket.ticketsExpire is true and +# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, +# this controls the minimum period for which tickets are valid. +# The default is PT1H for one hour. +authentication.ticket.validDuration=PT1H + +# Use one ticket for all user sessions +# For the pre 4.2 behaviour of one ticket per session set this to false. +authentication.ticket.useSingleTicketPerUser=true + +authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true +authentication.getRemoteUserTimeoutMilliseconds=10000 + +# FTP access +ftp.enabled=false + +# Default root path for protocols +protocols.storeName=${spaces.store} +protocols.rootPath=/${spaces.company_home.childname} + +# OpenCMIS +opencmis.connector.default.store=${spaces.store} +opencmis.connector.default.rootPath=/${spaces.company_home.childname} +opencmis.connector.default.typesDefaultMaxItems=500 +opencmis.connector.default.typesDefaultDepth=-1 +opencmis.connector.default.objectsDefaultMaxItems=10000 +opencmis.connector.default.objectsDefaultDepth=100 +opencmis.connector.default.contentChangesDefaultMaxItems=10000 +opencmis.connector.default.openHttpSession=false +opencmis.activities.enabled=true +opencmis.bulkUpdateProperties.maxItemsSize=1000 +opencmis.bulkUpdateProperties.batchSize=20 +opencmis.bulkUpdateProperties.workerThreads=2 +opencmis.maxContentSizeMB=4096 +opencmis.memoryThresholdKB=4096 + +# URL generation overrides + +# if true, the context path of OpenCMIS generated urls will be set to "opencmis.context.value", otherwise it will be taken from the request url +opencmis.context.override=false +opencmis.context.value= +# if true, the servlet path of OpenCMIS generated urls will be set to "opencmis.servletpath.value", otherwise it will be taken from the request url +opencmis.servletpath.override=false +opencmis.servletpath.value= +opencmis.server.override=false +opencmis.server.value= + +# IMAP +imap.server.enabled=false +imap.server.port=143 +imap.server.attachments.extraction.enabled=true + +# Default IMAP mount points +imap.config.home.store=${spaces.store} +imap.config.home.rootPath=/${spaces.company_home.childname} +imap.config.home.folderPath=${spaces.imap_home.childname} +imap.config.server.mountPoints=AlfrescoIMAP +imap.config.server.mountPoints.default.mountPointName=IMAP +imap.config.server.mountPoints.default.modeName=ARCHIVE +imap.config.server.mountPoints.default.store=${spaces.store} +imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} +imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP +imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED + +#Imap extraction settings +#imap.attachments.mode: +# SEPARATE -- All attachments for each email will be extracted to separate folder. +# COMMON -- All attachments for all emails will be extracted to one folder. +# SAME -- Attachments will be extracted to the same folder where email lies. +imap.attachments.mode=SEPARATE +imap.attachments.folder.store=${spaces.store} +imap.attachments.folder.rootPath=/${spaces.company_home.childname} +imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} + +# Activities Feed - refer to subsystem + +# Feed max ID range to limit maximum number of entries +activities.feed.max.idRange=1000000 +# Feed max size (number of entries) +activities.feed.max.size=200 +# Feed max age (eg. 44640 mins => 31 days) +activities.feed.max.ageMins=44640 + +activities.feed.generator.jsonFormatOnly=true +activities.feed.fetchBatchSize=250 +activities.feedNotifier.batchSize=200 +activities.feedNotifier.numThreads=2 + +# Subsystem unit test values. Will not have any effect on production servers +subsystems.test.beanProp.default.longProperty=123456789123456789 +subsystems.test.beanProp.default.anotherStringProperty=Global Default +subsystems.test.beanProp=inst1,inst2,inst3 +subsystems.test.beanProp.value.inst2.boolProperty=true +subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default +subsystems.test.simpleProp2=true +subsystems.test.simpleProp3=Global Default3 + +# Default Async Action Thread Pool +default.async.action.threadPriority=1 +default.async.action.corePoolSize=8 +default.async.action.maximumPoolSize=20 + +# Deployment Service +deployment.service.numberOfSendingThreads=5 +deployment.service.corePoolSize=2 +deployment.service.maximumPoolSize=3 +deployment.service.threadPriority=5 +# How long to wait in mS before refreshing a target lock - detects shutdown servers +deployment.service.targetLockRefreshTime=60000 +# How long to wait in mS from the last communication before deciding that deployment has failed, possibly +# the destination is no longer available? +deployment.service.targetLockTimeout=3600000 +# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ANSIBLE, ZIP, QUICK_START) +deployment.method=DEFAULT + +#Invitation Service +# Should send emails as part of invitation process. +notification.email.siteinvite=true +# Moderated invite Activiti workflow +site.invite.moderated.workflowId=activiti$activitiInvitationModerated +# Add intneral users Activiti workflow (use activiti$activitiInvitationNominated to revert to requiring accept of invite for internal users) +site.invite.nominated.workflowId=activiti$activitiInvitationNominatedAddDirect +# Add external users Activiti workflow +site.invite.nominatedExternal.workflowId=activiti$activitiInvitationNominated + +# Replication Service +replication.enabled=false + +# Transfer Service +transferservice.receiver.enabled=false +transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging +# +# How long to wait in mS before refreshing a transfer lock - detects shutdown servers +# Default 1 minute. +transferservice.receiver.lockRefreshTime=60000 +# +# How many times to attempt retry the transfer lock +transferservice.receiver.lockRetryCount=3 +# How long to wait, in mS, before retrying the transfer lock +transferservice.receiver.lockRetryWait=100 +# +# How long to wait, in mS, since the last contact with from the client before +# timing out a transfer. Needs to be long enough to cope with network delays and "thinking +# time" for both source and destination. Default 5 minutes. +transferservice.receiver.lockTimeOut=300000 + +# OrphanReaper +orphanReaper.lockRefreshTime=60000 +orphanReaper.lockTimeOut=3600000 + + +# security +security.anyDenyDenies=true +# Whether to post-process denies. Only applies to solr4+ when anyDenyDenies is true. +security.postProcessDenies=false + +# +# Encryption properties +# +# default keystores location +dir.keystore=classpath:alfresco/keystore + +# general encryption parameters +encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator +encryption.keyAlgorithm=AES +encryption.cipherAlgorithm=AES/CBC/PKCS5Padding + +# secret key keystore configuration +encryption.keystore.location=${dir.keystore}/keystore +# configuration via metadata is deprecated +encryption.keystore.keyMetaData.location= +encryption.keystore.provider= +encryption.keystore.type=pkcs12 + +# backup secret key keystore configuration +encryption.keystore.backup.location=${dir.keystore}/backup-keystore +# configuration via metadata is deprecated +encryption.keystore.backup.keyMetaData.location= +encryption.keystore.backup.provider= +encryption.keystore.backup.type=pkcs12 + +# Should encryptable properties be re-encrypted with new encryption keys on botstrap? +encryption.bootstrap.reencrypt=false + +# mac/md5 encryption +encryption.mac.messageTimeout=30000 +encryption.mac.algorithm=HmacSHA1 + +# ssl encryption +encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore +encryption.ssl.keystore.provider= +encryption.ssl.keystore.type=JCEKS +# configuration via metadata is deprecated +encryption.ssl.keystore.keyMetaData.location= +encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore +encryption.ssl.truststore.provider= +encryption.ssl.truststore.type=JCEKS +# configuration via metadata is deprecated +encryption.ssl.truststore.keyMetaData.location= + +# Re-encryptor properties +encryption.reencryptor.chunkSize=100 +encryption.reencryptor.numThreads=2 + +# SOLR connection details (e.g. for JMX) +solr.host=localhost +solr.port=8983 +solr.port.ssl=8984 +solr.solrUser=solr +solr.solrPassword=solr +# none, https +solr.secureComms=https +solr.sharedSecret= +solr.sharedSecret.header=X-Alfresco-Search-Secret +solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY + +solr.max.total.connections=40 +solr.max.host.connections=40 + +# Solr connection timeouts +# solr connect timeout in ms +solr.solrConnectTimeout=5000 + +# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away +solr.solrPingCronExpression=0 0/5 * * * ? * + + +#Default SOLR store mappings mappings +solr.store.mappings=solrMappingAlfresco,solrMappingArchive +solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco +solr.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive +solr.store.mappings.value.solrMappingArchive.protocol=archive +solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore + +#Default SOLR 4 store mappings mappings +solr4.store.mappings=solrMappingAlfresco,solrMappingArchive +solr4.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr4.store.mappings.value.solrMappingAlfresco.baseUrl=/solr4/alfresco +solr4.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr4.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr4.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr4.store.mappings.value.solrMappingArchive.baseUrl=/solr4/archive +solr4.store.mappings.value.solrMappingArchive.protocol=archive +solr4.store.mappings.value.solrMappingArchive.identifier=SpacesStore + +#Default SOLR 6 store mappings mappings +solr6.store.mappings=solrMappingAlfresco,solrMappingArchive,solrMappingHistory +solr6.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco +solr6.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr6.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr6.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive +solr6.store.mappings.value.solrMappingArchive.protocol=archive +solr6.store.mappings.value.solrMappingArchive.identifier=SpacesStore +solr6.store.mappings.value.solrMappingHistory.httpClientFactory=solrHttpClientFactory +solr6.store.mappings.value.solrMappingHistory.baseUrl=/solr/history +solr6.store.mappings.value.solrMappingHistory.protocol=workspace +solr6.store.mappings.value.solrMappingHistory.identifier=history + +# +# URL Shortening Properties +# +urlshortening.bitly.username=brianalfresco +urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f +urlshortening.bitly.url.length=20 + +# +# Bulk Filesystem Importer +# + +# The number of threads to employ in a batch import +bulkImport.batch.numThreads=4 + +# The size of a batch in a batch import i.e. the number of files to import in a +# transaction/thread +bulkImport.batch.batchSize=20 + + +# +# Caching Content Store +# +system.content.caching.cacheOnInbound=true +system.content.caching.maxDeleteWatchCount=1 +# Clean up every day at 3 am +system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? +system.content.caching.minFileAgeMillis=60000 +system.content.caching.maxUsageMB=4096 +# maxFileSizeMB - 0 means no max file size. +system.content.caching.maxFileSizeMB=0 +# When the CachingContentStore is about to write a cache file but the disk usage is in excess of panicThresholdPct +# (default 90%) then the cache file is not written and the cleaner is started (if not already running) in a new thread. +system.content.caching.panicThresholdPct=90 +# When a cache file has been written that results in cleanThresholdPct (default 80%) of maxUsageBytes +# being exceeded then the cached content cleaner is invoked (if not already running) in a new thread. +system.content.caching.cleanThresholdPct=80 +# An aggressive cleaner is run till the targetUsagePct (default 70%) of maxUsageBytes is achieved +system.content.caching.targetUsagePct=70 +# Threshold in seconds indicating a minimal gap between normal cleanup starts +system.content.caching.normalCleanThresholdSec=0 + +mybatis.useLocalCaches=false + +fileFolderService.checkHidden.enabled=true + + +ticket.cleanup.cronExpression=0 0 * * * ? + +# +# Download Service Cleanup +# +download.cleaner.startDelayMilliseconds=3600000 +# 1 hour +download.cleaner.repeatIntervalMilliseconds=3600000 +download.cleaner.maxAgeMins=60 +# -1 or 0 for not using batches +download.cleaner.batchSize=1000 + +# you could set this to false for new installations greater then ACS 6.2 +# see MNT-20212 +download.cleaner.cleanAllSysDownloadFolders=true + +# +# Download Service Limits, in bytes +# +download.maxContentSize=2152852358 + +# Max size of view trashcan files +# +trashcan.MaxSize=1000 + +# +# Use bridge tables for caching authority evaluation. +# +authority.useBridgeTable=true + +# Limit the number of results from findAuthority query +authority.findAuthorityLimit=10000 + +# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden +system.quickshare.enabled=true +system.quickshare.email.from.default=noreply@alfresco.com +# By default the difference between the quick share expiry date and the current time must be at least 1 day (24 hours). +# However, this can be changed to at least 1 hour or 1 minute for testing purposes. For example, +# setting the value to MINUTES, means the service will calculate the difference between NOW and the given expiry date +# in terms of minutes and checks for the difference to be greater than 1 minute. +# DAYS | HOURS | MINUTES +system.quickshare.expiry_date.enforce.minimum.period=DAYS + +# Oubound Mail +mail.service.corePoolSize=8 +mail.service.maximumPoolSize=20 + +nodes.bulkLoad.cachingThreshold=10 + +# Multi-Tenancy + +# if "dir.contentstore.tenants" is set then +# tenants are not co-mingled and all content roots will appear below this container (in sub-folder) +# and when creating a tenant the "contentRootPath" (root content store directory for a given tenant) will be ignored +dir.contentstore.tenants= + +# Gateway Authentication +# gateway authentication is disabled if empty host is specified +alfresco.authentication.gateway.host= +alfresco.authentication.gateway.protocol=https +alfresco.authentication.gateway.port=443 +alfresco.authentication.gateway.outboundHeaders=Authorization,key +alfresco.authentication.gateway.inboundHeaders=X-Alfresco-Authenticator-Key,X-Alfresco-Remote-User +alfresco.authentication.gateway.prefixUrl=/publicapi +alfresco.authentication.gateway.bufferSize=2048 +alfresco.authentication.gateway.connectTimeout=10000 +alfresco.authentication.gateway.readTimeout=120000 +alfresco.authentication.gateway.httpTcpNodelay=true +alfresco.authentication.gateway.httpConnectionStalecheck=true + +# webscripts config +webscripts.encryptTempFiles=false +webscripts.tempDirectoryName=Alfresco-WebScripts +# 4mb +webscripts.memoryThreshold=4194304 +# 4gb +webscripts.setMaxContentSize=5368709120 + +# Property to enable index upgrade for metadata query (MDQ) +# +# The indexes are not added unless this value is changed +# Adding each the supporting indexes may take several hours depending on the size of the database. +# The required indexes may be added in stages. +# See: classpath:alfresco/dbscripts/upgrade/4.2/${db.script.dialect}/metadata-query-indexes.sql +# See: classpath:alfresco/dbscripts/upgrade/5.1/${db.script.dialect}/metadata-query-indexes-2.sql +system.metadata-query-indexes.ignored=true +system.metadata-query-indexes-more.ignored=true + +# +# Do we defer running the shared folder patch? +# +system.patch.sharedFolder.deferred=false +# Default value is run new years day 2030 i.e. not run. +system.patch.sharedFolder.cronExpression=0 0 0 ? 1 1 2030 + +# +# Default values for deferring the running of the addUnmovableAspect patch +# +system.patch.addUnmovableAspect.deferred=false +system.patch.addUnmovableAspect.cronExpression=0 0 0 ? 1 1 2030 + +# Property to enable removal of all JBPM related data from the database +# +# The tables are not removed from the databasen unless explicitly requested by setting this property to false. +# See: classpath:alfresco/dbscripts/upgrade/5.2/${db.script.dialect}/remove-jbpm-tables-from-db.sql +system.remove-jbpm-tables-from-db.ignored=true + +# +# Use a canned query when requested to search for people if " [hint:useCQ]" is provided in search term +# +people.search.honor.hint.useCQ=true + +# Delays cron jobs after bootstrap to allow server to fully come up before jobs start +system.cronJob.startDelayMilliseconds=60000 + +# Schedule for reading mimetype config definitions dynamically. Initially checks every 10 seconds and then switches to +# every hour after the configuration is read successfully. If there is a error later reading the config, the +# checks return to every 10 seconds. +mimetype.config.cronExpression=0 30 0/1 * * ? +mimetype.config.initialAndOnError.cronExpression=0/10 * * * * ? + +# Optional property to specify an external file or directory that will be read for mimetype definitions from YAML +# files (possibly added to a volume via k8 ConfigMaps). +mimetype.config.dir=shared/classes/alfresco/extension/mimetypes + +# Schedule for reading rendition config definitions dynamically. Initially checks every 10 seconds and then switches to +# every hour after the configuration is read successfully. If there is a error later reading the config, the +# checks return to every 10 seconds. +rendition.config.cronExpression=2 30 0/1 * * ? +rendition.config.initialAndOnError.cronExpression=0/10 * * * * ? + +# Optional property to specify an external file or directory that will be read for rendition definitions from YAML +# files (possibly added to a volume via k8 ConfigMaps). +rendition.config.dir=shared/classes/alfresco/extension/transform/renditions + +# Optional property to specify an external file or directory that will be read for transformer json config. +local.transform.pipeline.config.dir=shared/classes/alfresco/extension/transform/pipelines + +# Used to disable transforms locally. +local.transform.service.enabled=true + +# Schedule for reading local transform config, so that T-Engines and local pipeline config is dynamically +# picked up, or reintegrated after an outage. Initially checks every 10 seconds and then switches to every hour +# after the configuration is read successfully. If there is a error later reading the config, the checks return to +# every 10 seconds. +local.transform.service.cronExpression=4 30 0/1 * * ? +local.transform.service.initialAndOnError.cronExpression=0/10 * * * * ? + +# +# Check that the declared mimetype (of the Node) is the same as the derived +# mimetype of the content (via Tika) before a transformation takes place. +# Only files in the repository (not intermediate files in a transformer +# pipeline) are checked. This property provides a trade off between a +# security check and a relatively expensive (Tika) operation. +# +# There are a few issues with the Tika mimetype detection. So that transformations +# still take place where the detected mimetype is not the same as the declared mimetype, +# another property (transformer.strict.mimetype.check.whitelist.mimetypes) contains pairs +# of declared and detected mimetypes that should be allowed. This parameter value is a +# sequence of ; separated pairs. The declared and derived mimetypes are also ; separated. +# +transformer.strict.mimetype.check=true + +# A white list of declared and detected mimetypes, that don't match, but should still be transformed. +transformer.strict.mimetype.check.whitelist.mimetypes=application/eps;application/postscript;application/illustrator;application/pdf;application/x-tar;application/x-gtar;application/acp;application/zip;application/vnd.stardivision.math;application/x-tika-msoffice + +# +# Enable transformation retrying if the file has MIME type differ than file extension. +# Ignored if transformer.strict.mimetype.check is true as these transformations +# will not take place. +# +content.transformer.retryOn.different.mimetype=true + +# Debug and Log buffer sizes +transformer.debug.entries=0 +transformer.log.entries=50 + +# +# Lock timeout configuration +# +system.lockTryTimeout=100 +system.lockTryTimeout.DictionaryDAOImpl=10000 +system.lockTryTimeout.MessageServiceImpl=${system.lockTryTimeout} +system.lockTryTimeout.PolicyComponentImpl=${system.lockTryTimeout} + + +# Scheduled job to clean up unused properties from the alf_prop_xxx tables. +# Default setting of "0 0 3 ? * SAT" is to run every Saturday at 3am. +attributes.propcleaner.cronExpression=0 0 3 ? * SAT + +# Control Alfresco JMX connectivity +alfresco.jmx.connector.enabled=false + +# Dissallow Attribute Service Entries with "Serializable" objects in key Segments +# Please, see MNT-11895 for details. +system.propval.uniquenessCheck.enabled=true + +# Requests for ephemeral (in-memory) locks with expiry times (in seconds) greater +# than this value will result in persistent locks being created instead. By default +# this value is equal to the maximum allowed expiry for ephemeral locks, therefore +# this feature is disabled by default. Setting this to -1 would mean that ALL +# requests for ephemeral locks would result in persistent locks being created. +alfresco.ephemeralLock.expiryThresh=172800 + +# SurfConfigFolder Patch +# +# Do we defer running the surf-config folder patch? +# +system.patch.surfConfigFolder.deferred=false +# Default value. i.e. never run. It can be triggered using JMX +system.patch.surfConfigFolder.cronExpression=* * * * * ? 2099 + +# +# Solr Facets Config Properties +# +solr_facets.root.path=/app:company_home/app:dictionary +solr_facets.root=${solr_facets.root.path}/${spaces.solr_facets.root.childname} +solr_facets.inheritanceHierarchy=default,custom + +models.enforceTenantInNamespace=false + +# Allowed protocols for links +links.protocosl.white.list=http,https,ftp,mailto + +# Fixed ACLs +# Required for fixing MNT-15368 - Time Consumed for Updating Folder Permission +# ADMAccessControlListDAO.setFixedAcls called on a large folder hierarchy will take a long time for its execution. +# For this reason now method can also be called asynchronously if transaction reaches system.fixedACLs.maxTransactionTime. +# In this case setFixedAcls method recursion will be stopped and unfinished nodes will be marked with ASPECT_PENDING_FIX_ACL. +# Pending nodes will be processed by FixedAclUpdater, programmatically called but also configured as a scheduled job. +system.fixedACLs.maxTransactionTime=10000 +# fixedACLsUpdater - lock time to live +system.fixedACLsUpdater.lockTTL=10000 +# fixedACLsUpdater - maximum number of nodes to process per execution +system.fixedACLsUpdater.maxItemBatchSize=100 +# fixedACLsUpdater - the number of threads to use +system.fixedACLsUpdater.numThreads=4 +# fixedACLsUpdater - Force shared ACL to propagate through children even if there is an unexpected ACL +system.fixedACLsUpdater.forceSharedACL=false +# fixedACLsUpdater cron expression - fire at midnight every day +system.fixedACLsUpdater.cronExpression=0 0 0 * * ? + +cmis.disable.hidden.leading.period.files=false + +#Smart Folders Config Properties +smart.folders.enabled=false + +#Smart reference config +smart.reference.classpath.hash=${smart.folders.config.vanilla.processor.classpath}->1,${smart.folders.config.system.templates.classpath}->2 + +#Smart store config + +#Company home relative download associations of smart entries +smart.download.associations.folder=${spaces.dictionary.childname}/${spaces.smartdownloads.childname} + +#Generic virtualization methods config + +#Vanilla JSON templates javascript processor classpath. A java script processor used to +#covert JSON templates to internal smart folder definitions. + +smart.folders.config.vanilla.processor.classpath=/org/alfresco/repo/virtual/node/vanilla.js + +#System virtualization method config + +#System virtualization method aspect. +smart.folders.config.system.aspect=smf:systemConfigSmartFolder +#System virtualization method aspect defined template location property. +smart.folders.config.system.aspect.template.location.property=smf:system-template-location +#Classpath to be explored for *.json entries defining system templates. +smart.folders.config.system.templates.classpath=/org/alfresco/repo/virtual/node +#A company home relative name or qname path location of repository system templates. +smart.folders.config.system.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} +#Content sub type of repository system templates. +smart.folders.config.system.templates.template.type=smf:smartFolderTemplate + +#Custom virtualization method config + +#Custom virtualization method aspect. +smart.folders.config.custom.aspect=smf:customConfigSmartFolder +#Custom virtualization method aspect template content association. +smart.folders.config.custom.aspect.template.association=smf:custom-template-association + + +#Type virtualization method config + +#A company home relative name or qname path location of the type mapped templates. +smart.folders.config.type.templates.path=${spaces.dictionary.childname}/${spaces.smartfolders.childname} +#Type and aspect qname regular expression filter. +smart.folders.config.type.templates.qname.filter=none + +# Preferred password encoding, md4, sha256, bcrypt10 +system.preferred.password.encoding=md4 + +# Upgrade Password Hash Job +system.upgradePasswordHash.jobBatchSize=100 +system.upgradePasswordHash.jobQueryRange=10000 +system.upgradePasswordHash.jobThreadCount=4 +system.upgradePasswordHash.jobCronExpression=* * * * * ? 2099 + +system.api.discovery.enabled=true + +# Maximum query size for category/tag fetch when not explicitly set by paging parameters +category.queryFetchSize=5000 + +# Brute force protection +authentication.protection.enabled=true +authentication.protection.limit=10 +authentication.protection.periodSeconds=6 + +system.email.sender.default=noreply@alfresco.com +# reset password workflow will expire in an hour +system.reset-password.endTimer=PT1H +system.reset-password.sendEmailAsynchronously=true + +# HeartBeat +heartbeat.target.url= +heartbeat.enabled=true + +# CSRF filter overrides +csrf.filter.enabled=true +csrf.filter.referer= +csrf.filter.referer.always=false +csrf.filter.origin= +csrf.filter.origin.always=false + +# CORS settings +cors.enabled=false +cors.allowed.origins= +cors.allowed.methods=GET,POST,HEAD,OPTIONS,PUT,DELETE +cors.allowed.headers=Authorization,Content-Type,Cache-Control,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,X-CSRF-Token +cors.exposed.headers=Access-Control-Allow-Origin,Access-Control-Allow-Credentials +cors.support.credentials=true +cors.preflight.maxage=10 + +# Alfresco Rest Api-Explorer +api-explorer.url= + +# Events subsystem +events.subsystem.autoStart=false +# Messaging subsystem +messaging.subsystem.autoStart=true + + +# Raw events +acs.repo.rendition.events.endpoint=jms:acs-repo-rendition-events?jmsMessageType=Text + +# Transform request events +acs.repo.transform.request.endpoint=jms:acs-repo-transform-request?jmsMessageType=Text + +# If enabled doesn't allow to set content properties via NodeService +contentPropertyRestrictions.enabled=true +contentPropertyRestrictions.whitelist= + +# Repo events2 +# Type and aspect filters which should be excluded +# Note: System folders node types are added by default +repo.event2.filter.nodeTypes=sys:*, fm:*, cm:thumbnail, cm:failedThumbnail, cm:rating, rma:rmsite include_subtypes +repo.event2.filter.nodeAspects=sys:* +repo.event2.filter.childAssocTypes=rn:rendition +# Comma separated list of users which should be excluded +# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting +repo.event2.filter.users= +# Topic name +repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2 +# Thread pool for async enqueue of repo events +repo.event2.queue.enqueueThreadPool.priority=1 +repo.event2.queue.enqueueThreadPool.coreSize=8 +repo.event2.queue.enqueueThreadPool.maximumSize=10 +# Thread pool for async dequeue and delivery of repo events +repo.event2.queue.dequeueThreadPool.priority=1 +repo.event2.queue.dequeueThreadPool.coreSize=1 +repo.event2.queue.dequeueThreadPool.maximumSize=1 + + +# MNT-21083 +# --DELETE_NOT_EXISTS - default settings +system.delete_not_exists.batchsize=100000 +system.delete_not_exists.delete_batchsize=1000 +system.delete_not_exists.read_only=false +system.delete_not_exists.timeout_seconds=-1 +system.prop_table_cleaner.algorithm=V2 + +# Configure the system-wide (ACS) settings for direct access urls. +# +# For Direct Access URLs to be usable on the service-layer, the feature must be enabled both system-wide and on the +# content-store(s). For the feature to be usable through REST (outside the JVM) the rest-api configuration must also be +# enabled. +# +# The system.directAccessUrl.enabled property is the main switch of the feature. If this is set to false ALL +# Direct Access URLs are disabled. +# +# The next configuration that controls specific Direct Access URLs is the content store one. +# The connector.s3.directAccessUrl.enabled property controls whether Direct Access URLs are enabled for that specific store. +# +# Whether or not a client can request a Direct Access URL by using a REST endpoint is controlled by the +# restApi.directAccessUrl.enabled property. If the REST endpoint is disabled, but the feature is enabled +# system-wide and on the content-store, then the direct access URLs will only be usable by Java clients (only +# service-level requests will be possible). + +# Controls whether this feature is available, system wide. +# For direct access urls to work, the feature needs to be enabled both system-wide and on the individual content-store. +system.directAccessUrl.enabled=false +# Sets the default expiry time for the direct access url across all Content Stores. +# Its value cannot exceed the system-wide max expiry time, it can only be equal or lower (all DAUs disabled otherwise). +# This property is mandatory if direct access urls are enabled system-wide - (all DAUs disabled otherwise). +system.directAccessUrl.defaultExpiryTimeInSec=30 +# Sets the upper limit for the direct access urls expiry time, meaning a Content Store will be able to override this +# value but not exceed it, and the same goes for the clients. +# A service (Java Interface) client will be able to request a direct access url for a custom expiry time but that time +# can’t exceed this value. +# If the requested time exceeds the max value, the expiry time reverts to the default configured one. +# This property is mandatory if direct access urls are enabled system-wide - (all DAUs disabled otherwise). +system.directAccessUrl.maxExpiryTimeInSec=300 + +# Configure the common S3 storage connector content store settings for direct access urls. +# +# Note: When multiple S3 buckets are used for storage in Alfresco, each S3 Content Stores can be configured with either +# the default (common) S3 Connector-specific properties (i.e. connector.s3.directAccessUrl.enabled etc.) OR new separate +# properties could be defined for each and every store (e.g. +# connector.s3store1.directAccessUrl.enabled, +# connector.s3store2.directAccessUrl.enabled etc.). + +# Controls whether DAUs are enabled on this specific content store. +# For direct access urls to work, the feature needs to be enabled both system-wide and on the individual content-store. +connector.s3.directAccessUrl.enabled=false +# Sets the expiry time for the direct access url in this store, by overriding the system-wide config. +# If this value exceeds the content store upper limit or the system-wide default it will fallback to the system-wide +# default configuration. +# Its value cannot exceed the system-wide max expiry time, it can only be equal or lower (DAUs for the specific content +# store disabled otherwise). +# If not set, the default system-wide setting will be used. +connector.s3.directAccessUrl.defaultExpiryTimeInSec=30 +# The maximum expiry time interval that can be requested by clients - content-store specific setting. +# Its value cannot exceed the system-wide configuration, it can only be equal or lower (DAUs for the specific content +# store disabled otherwise). +# If not set, the default system-wide setting will be used. +connector.s3.directAccessUrl.maxExpiryTimeInSec=300 + +# Configure the REST API configuration settings for direct access urls. +# +# Controls whether direct access url requests via the REST API are enabled. +restApi.directAccessUrl.enabled=false +# Sets the expiry time for all the direct access urls requested via a REST call. +# Its value cannot exceed the system-wide max expiry time configuration, it can only be equal or lower (REST API DAUs +# disabled otherwise). +# If not set, the default system-wide setting will be used. +# Direct Access Url REST API calls cannot request an explicit expiry time. +restApi.directAccessUrl.defaultExpiryTimeInSec=30 + +# Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories. +system.new-node-transaction-indexes.ignored=true + +# Allows the configuration of maximum limits of the temp files to be deleted or the maximum time allowed to run for the job +system.tempFileCleaner.maxFilesToDelete= +system.tempFileCleaner.maxTimeToRun= + +# Property to long running migration to remove alf_server in v7+ patch.db-V7.1.0-remove-alf_server-table +system.remove-alf_server-table-from-db.ignored=true diff --git a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java index 75a8e4c9c7..7836b7cbec 100644 --- a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java +++ b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java @@ -180,6 +180,9 @@ import org.junit.runners.Suite; org.alfresco.repo.audit.AuditableAnnotationTest.class, org.alfresco.repo.audit.PropertyAuditFilterTest.class, org.alfresco.repo.audit.access.NodeChangeTest.class, + org.alfresco.repo.content.ContentServiceImplUnitTest.class, + org.alfresco.repo.content.directurl.SystemWideDirectUrlConfigUnitTest.class, + org.alfresco.repo.content.directurl.ContentStoreDirectUrlConfigUnitTest.class, org.alfresco.repo.content.LimitedStreamCopierTest.class, org.alfresco.repo.content.filestore.FileIOTest.class, org.alfresco.repo.content.filestore.SpoofedTextContentReaderTest.class, diff --git a/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java new file mode 100644 index 0000000000..63e6530715 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java @@ -0,0 +1,160 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** + * Unit tests for content service implementation. + * + * @author Sara Aspery + */ +public class ContentServiceImplUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long SYS_DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long SYS_MAX_EXPIRY_TIME_IN_SECS = 300L; + + private static final NodeRef NODE_REF = new NodeRef("content://Node/Ref"); + + @InjectMocks + private ContentServiceImpl contentService; + + @Mock + private ContentStore mockContentStore; + + @Mock + private NodeService mockNodeService; + + @Mock + private ContentData mockContentData; + + @Before + public void setup() + { + openMocks(this); + when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_CONTENT)).thenReturn(mockContentData); + when(mockContentData.getContentUrl()).thenReturn("someContentUrl"); + when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_NAME)).thenReturn("someFilename"); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsDisabled() + { + setupSystemWideDirectAccessConfig(DISABLED); + assertFalse("Expected contentDirectUrl to be disabled", contentService.isContentDirectUrlEnabled()); + verify(mockContentStore, never()).isContentDirectUrlEnabled(); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsEnabledButStoreIsDisabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(DISABLED); + assertFalse("Expected contentDirectUrl to be disabled", contentService.isContentDirectUrlEnabled()); + } + + @Test + public void testIsContentDirectUrlEnabled_SystemWideIsEnabledAndStoreIsEnabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + assertTrue("Expected contentDirectUrl to be enabled", contentService.isContentDirectUrlEnabled()); + } + + @Test + public void testRequestContentDirectUrl_SystemWideIsDisabled() + { + setupSystemWideDirectAccessConfig(DISABLED); + try + { + contentService.requestContentDirectUrl(NODE_REF, true, 20L); + fail("Expected DirectAccessUrlDisabledException"); + } + catch (DirectAccessUrlDisabledException ex) + { + verify(mockContentStore, never()).isContentDirectUrlEnabled(); + } + } + + @Test + public void testRequestContentDirectUrl_SystemWideIsEnabledButStoreIsDisabled() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(DISABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, true, 20L); + assertNull(directAccessUrl); + verify(mockContentStore, never()).requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong()); + } + + @Test + public void testRequestContentDirectUrl_StoreIsEnabledButNotImplemented() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, true, 20L); + assertNull(directAccessUrl); + verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupSystemWideDirectAccessConfig(Boolean isEnabled) + { + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(isEnabled); + sysConfig.setDefaultExpiryTimeInSec(SYS_DEFAULT_EXPIRY_TIME_IN_SECS); + sysConfig.setMaxExpiryTimeInSec(SYS_MAX_EXPIRY_TIME_IN_SECS); + sysConfig.validate(); + contentService.setSystemWideDirectUrlConfig(sysConfig); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java index ffac9eea8b..f35d0b08d4 100644 --- a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java @@ -35,6 +35,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -493,10 +494,10 @@ public class CachingContentStoreTest @Test public void isDirectAccessSupported() { - assertFalse(cachingStore.isDirectAccessSupported()); + assertFalse(cachingStore.isContentDirectUrlEnabled()); - when(backingStore.isDirectAccessSupported()).thenReturn(true); - assertTrue(cachingStore.isDirectAccessSupported()); + when(backingStore.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(cachingStore.isContentDirectUrlEnabled()); } @Test @@ -504,8 +505,8 @@ public class CachingContentStoreTest { try { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenThrow(new UnsupportedOperationException()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong())).thenThrow(new UnsupportedOperationException()); + cachingStore.requestContentDirectUrl("url", true,"someFile", 30L); fail(); } catch (UnsupportedOperationException e) @@ -517,7 +518,7 @@ public class CachingContentStoreTest @Test public void getDirectAccessUrl() { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenReturn(new DirectAccessUrl()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), eq(true), anyString(), anyLong())).thenReturn(new DirectAccessUrl()); + cachingStore.requestContentDirectUrl("url", true,"someFile", 30L); } } diff --git a/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..b32ed63be8 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/directurl/ContentStoreDirectUrlConfigUnitTest.java @@ -0,0 +1,234 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for content store direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class ContentStoreDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 10L; + private static final Long MAX_EXPIRY_TIME_IN_SECS = 20L; + + private static final Long SYS_DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long SYS_MAX_EXPIRY_TIME_IN_SECS = 300L; + + private ContentStoreDirectUrlConfig contentStoreDirectUrlConfig; + + @Before + public void setup() + { + this.contentStoreDirectUrlConfig = new ContentStoreDirectUrlConfig(); + setupSystemWideDirectAccessConfig(); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertTrue("Expected REST API direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing_ValidReplacement() + { + Long maxExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, null, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, null, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing_ReplacementExceedsMax() + { + setupDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemMax() + { + Long defaultExpiryTimeInSecs = SYS_MAX_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsStoreMax_ValidReplacement() + { + Long maxExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + Long defaultExpiryTimeInSecs = maxExpiryTimeInSecs + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsStoreMax_ReplacementExceedsStoreMax() + { + Long defaultExpiryTimeInSecs = MAX_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemDefault_ValidReplacement() + { + Long defaultExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + Long maxExpiryTimeInSecs = SYS_MAX_EXPIRY_TIME_IN_SECS; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, maxExpiryTimeInSecs); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(ENABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSecs); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsSystemDefault_ReplacementExceedsStoreMax() + { + Long defaultExpiryTimeInSecs = SYS_DEFAULT_EXPIRY_TIME_IN_SECS + 1; + setupDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + + verifyDirectAccessConfig(ENABLED, defaultExpiryTimeInSecs, MAX_EXPIRY_TIME_IN_SECS); + contentStoreDirectUrlConfig.validate(); + verifyDirectAccessConfig(DISABLED, SYS_DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, 0L); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, -1L); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeExceedsSystemMax() + { + Long maxExpiryTimeInSec = contentStoreDirectUrlConfig.getSysWideMaxExpiryTimeInSec() + 1; + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, maxExpiryTimeInSec); + + assertTrue("Expected content store direct URLs to be enabled", contentStoreDirectUrlConfig.isEnabled()); + contentStoreDirectUrlConfig.validate(); + assertFalse("Expected content store direct URLs to be disabled", contentStoreDirectUrlConfig.isEnabled()); + } + + /* Helper method to set content store direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + contentStoreDirectUrlConfig.setEnabled(isEnabled); + contentStoreDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + contentStoreDirectUrlConfig.setMaxExpiryTimeInSec(maxExpiryTime); + } + + /* Helper method to verify content store direct access url configuration settings */ + private void verifyDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + assertEquals("Expected content store direct URLs to be enabled = " + isEnabled, isEnabled, contentStoreDirectUrlConfig.isEnabled()); + assertEquals("Expected default expiry time to match " + defaultExpiryTime, defaultExpiryTime, contentStoreDirectUrlConfig.getDefaultExpiryTimeInSec()); + assertEquals("Expected maximum expiry time to match " + maxExpiryTime, maxExpiryTime, contentStoreDirectUrlConfig.getMaxExpiryTimeInSec()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupSystemWideDirectAccessConfig() + { + SystemWideDirectUrlConfig sysConfig = new SystemWideDirectUrlConfig(); + sysConfig.setEnabled(ENABLED); + sysConfig.setDefaultExpiryTimeInSec(SYS_DEFAULT_EXPIRY_TIME_IN_SECS); + sysConfig.setMaxExpiryTimeInSec(SYS_MAX_EXPIRY_TIME_IN_SECS); + sysConfig.validate(); + contentStoreDirectUrlConfig.setSystemWideDirectUrlConfig(sysConfig); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java new file mode 100644 index 0000000000..d498fac30b --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/content/directurl/SystemWideDirectUrlConfigUnitTest.java @@ -0,0 +1,153 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.content.directurl; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for system-wide direct access URL configuration settings. + * + * @author Sara Aspery + */ +public class SystemWideDirectUrlConfigUnitTest +{ + private static final Boolean ENABLED = Boolean.TRUE; + private static final Boolean DISABLED = Boolean.FALSE; + + private static final Long DEFAULT_EXPIRY_TIME_IN_SECS = 30L; + private static final Long MAX_EXPIRY_TIME_IN_SECS = 300L; + + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + + @Before + public void setup() + { + this.systemWideDirectUrlConfig = new SystemWideDirectUrlConfig(); + } + + @Test + public void testValidConfig_RemainsEnabled() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testValidConfig_RemainsDisabled() + { + setupDirectAccessConfig(DISABLED, DEFAULT_EXPIRY_TIME_IN_SECS, MAX_EXPIRY_TIME_IN_SECS); + + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, null, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, 0L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, -1L, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeMissing() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, null); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeZero() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, 0L); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_MaxExpiryTimeNegative() + { + setupDirectAccessConfig(ENABLED, DEFAULT_EXPIRY_TIME_IN_SECS, -1L); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + @Test + public void testInvalidConfig_DefaultExpiryTimeExceedsMax() + { + setupDirectAccessConfig(ENABLED, MAX_EXPIRY_TIME_IN_SECS + 1, MAX_EXPIRY_TIME_IN_SECS); + + assertTrue("Expected system-wide direct URLs to be enabled", systemWideDirectUrlConfig.isEnabled()); + systemWideDirectUrlConfig.validate(); + assertFalse("Expected system-wide direct URLs to be disabled", systemWideDirectUrlConfig.isEnabled()); + } + + /* Helper method to set system-wide direct access url configuration settings */ + private void setupDirectAccessConfig(Boolean isEnabled, Long defaultExpiryTime, Long maxExpiryTime) + { + systemWideDirectUrlConfig.setEnabled(isEnabled); + systemWideDirectUrlConfig.setDefaultExpiryTimeInSec(defaultExpiryTime); + systemWideDirectUrlConfig.setMaxExpiryTimeInSec(maxExpiryTime); + } +} + diff --git a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java index ecfaf17823..585843f155 100644 --- a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -56,9 +56,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -191,7 +189,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes } @Test - public void testIsDirectAccessSupported() + public void testIsContentDirectUrlEnabled() { // Create the aggregating store AggregatingContentStore aggStore = new AggregatingContentStore(); @@ -199,21 +197,21 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes aggStore.setSecondaryStores(List.of(secondaryStoreMock)); // By default it is unsupported - assertFalse(aggStore.isDirectAccessSupported()); + assertFalse(aggStore.isContentDirectUrlEnabled()); // Supported if at least one store supports direct access { - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(false); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(false); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + assertTrue(aggStore.isContentDirectUrlEnabled()); } } @@ -229,15 +227,15 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggStore, ""); // By default it is unsupported - DirectAccessUrl directAccessUrl = aggStore.getDirectAccessUrl("url", null); + DirectAccessUrl directAccessUrl = aggStore.requestContentDirectUrl("url", true, "anyfilename", 30L); assertNull(directAccessUrl); // Direct access not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl(eq("urlDANotSupported"), true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -247,9 +245,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -259,9 +257,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -272,9 +270,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Content url not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggStore.getDirectAccessUrl("urlNotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlNotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedContentUrlException e) @@ -282,31 +280,31 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Expected } - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedExc); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedContentUrlExc); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); } } diff --git a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java index 5cebb8acaf..5423fd3077 100644 --- a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,18 +25,16 @@ */ package org.alfresco.repo.version; -import org.alfresco.error.AlfrescoRuntimeException; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.content.EmptyContentReader; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.MimetypeMapTest; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.namespace.QName; import org.alfresco.test_category.OwnJVMTestsCategory; @@ -44,10 +42,11 @@ import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; -import java.util.Date; /** * Tests for getting content readers and writers. @@ -57,7 +56,9 @@ import java.util.Date; @Category(OwnJVMTestsCategory.class) @Transactional public class ContentServiceImplTest extends BaseVersionStoreTest -{ +{ + private static final Boolean ENABLED = Boolean.TRUE; + /** * Test content data */ @@ -66,9 +67,14 @@ public class ContentServiceImplTest extends BaseVersionStoreTest /** * The version content store */ + @InjectMocks private ContentService contentService; + private ContentStore contentStore; + @Mock + private SystemWideDirectUrlConfig mockSystemWideDirectUrlConfig; + @Before public void before() throws Exception { @@ -139,15 +145,17 @@ public class ContentServiceImplTest extends BaseVersionStoreTest } @Test - public void testWhenGetDirectAccessUrlIsNotSupported() + public void testWhenRequestContentDirectUrlIsNotSupported() { - assertFalse(contentStore.isDirectAccessSupported()); + openMocks(this); + when(mockSystemWideDirectUrlConfig.isEnabled()).thenReturn(ENABLED); + when(mockSystemWideDirectUrlConfig.getDefaultExpiryTimeInSec()).thenReturn(30L); + when(mockSystemWideDirectUrlConfig.getMaxExpiryTimeInSec()).thenReturn(300L); + + assertFalse(contentStore.isContentDirectUrlEnabled()); // Set the presigned URL to expire after one minute. - Date expiresAt = new Date(); - long expTimeMillis = expiresAt.getTime(); - expTimeMillis += 1000 * 60; - expiresAt.setTime(expTimeMillis); + Long validFor = 60L; try { @@ -155,7 +163,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest NodeRef nodeRef = this.dbNodeService .createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}MyNoContentNode"), TEST_TYPE_QNAME, this.nodeProperties).getChildRef(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, validFor)); fail("nodeRef has no content"); } catch (IllegalArgumentException e) @@ -165,7 +173,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest try { - assertEquals(null, contentService.getDirectAccessUrl(null, null)); + assertNull(contentService.requestContentDirectUrl(null, true, null)); fail("nodeRef is null"); } catch (IllegalArgumentException e) @@ -176,7 +184,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest // Create a node with content NodeRef nodeRef = createNewVersionableNode(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, null)); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, null)); + assertNull(contentService.requestContentDirectUrl(nodeRef, true, validFor)); } }