Merge branch 'master' into fix/RM-6941_Retention_ActionOnFrozenNodes

This commit is contained in:
bdwiwedi
2021-08-13 10:48:18 +05:30
83 changed files with 5168 additions and 2799 deletions

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<String> nodeIDList)
{
List<String> 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<String> nodeRefs)
{
final JSONObject requestParams = new JSONObject();
requestParams.put("nodeRefs", new JSONArray(nodeRefs));
return doPostJsonRequest(user, password, expectedStatusCode, requestParams, EXPORT_API);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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());
}
}

View File

@@ -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
*

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -102,9 +102,11 @@
<bean id="rma.unfiledRecordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.UnfiledRecordFolderType" parent="rm.baseBehaviour" />
<bean id="rma.recordCategory" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordCategoryType" parent="rm.baseBehaviour">
<property name="recordService" ref="RecordService" />
<property name="vitalRecordService" ref="VitalRecordService" />
<property name="filePlanPermissionService" ref="FilePlanPermissionService" />
<property name="recordFolderService" ref="RecordFolderService" />
<property name="dispositionService" ref="DispositionService" />
</bean>
<bean id="rma.recordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordFolderType" parent="rm.baseBehaviour">

View File

@@ -8,13 +8,13 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<properties>
<app.amp.client.war.folder>${project.build.directory}/${project.build.finalName}-war</app.amp.client.war.folder>
<image.name>alfresco/alfresco-governance-repository-community</image.name>
<image.name>alfresco/alfresco-governance-repository-community-base</image.name>
</properties>
<dependencies>

View File

@@ -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());
}
}

View File

@@ -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<QName> ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>();
private final static List<QName> 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
*/

View File

@@ -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<Object>()
{
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

View File

@@ -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
* <p>
* 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<String>) 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
* <p>
* 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<String>) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS));
assertEquals(((List<String>) ((List<String>) nodeService.getProperty(record,
PROP_RS_DISPOSITION_EVENTS))).size(), 1);
assertEquals(DEFAULT_EVENT_NAME, ((List<String>) ((List<String>) 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<QName, Serializable> step = new HashMap<QName, Serializable>(3);

View File

@@ -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";

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<build>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -9,6 +9,6 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
</project>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<modules>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -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

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<developers>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<properties>

View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -85,7 +85,7 @@
<dependency.jboss.logging.version>3.4.2.Final</dependency.jboss.logging.version>
<dependency.camel.version>3.7.4</dependency.camel.version>
<dependency.activemq.version>5.16.1</dependency.activemq.version>
<dependency.apache-compress.version>1.20</dependency.apache-compress.version>
<dependency.apache-compress.version>1.21</dependency.apache-compress.version>
<dependency.apache.taglibs.version>1.2.5</dependency.apache.taglibs.version>
<dependency.awaitility.version>4.1.0</dependency.awaitility.version>
<dependency.swagger-ui.version>3.38.0</dependency.swagger-ui.version>
@@ -112,7 +112,7 @@
<dependency.mysql.version>8.0.25</dependency.mysql.version>
<dependency.mysql-image.version>8</dependency.mysql-image.version>
<dependency.mariadb.version>2.7.2</dependency.mariadb.version>
<dependency.tas-utility.version>3.0.44</dependency.tas-utility.version>
<dependency.tas-utility.version>3.0.45</dependency.tas-utility.version>
<dependency.rest-assured.version>3.3.0</dependency.rest-assured.version>
<dependency.tas-restapi.version>1.61</dependency.tas-restapi.version>
<dependency.tas-cmis.version>1.30</dependency.tas-cmis.version>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -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<TempOutputStream> 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<TempOutputStream> 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;

View File

@@ -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<TempOutputStream> 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<TempOutputStream> 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;
}
}
}

View File

@@ -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<TempOutputStream> 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<String, Object> getScriptParameters()
{
Map<String, Object> params = new HashMap<String, Object>();
params.putAll(super.getScriptParameters());
Map<String, Object> params = new HashMap<>(super.getScriptParameters());
addRepoParameters(params);
return params;
}
@@ -259,16 +255,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer
public Map<String, Object> getTemplateParameters()
{
// Ensure we have a transaction - we might be generating the status template after the main transaction failed
return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback<Map<String, Object>>()
{
public Map<String, Object> execute() throws Throwable
{
Map<String, Object> params = new HashMap<String, Object>();
params.putAll(RepositoryContainer.super.getTemplateParameters());
params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
addRepoParameters(params);
return params;
}
return fallbackTransactionHelper.doInTransaction(() -> {
Map<String, Object> 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<Boolean> authWork = new RetryingTransactionCallback<Boolean>()
{
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<Boolean> 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<Object> work = new RetryingTransactionCallback<Object>()
{
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<Object> work = new RunAsWork<Object>()
{
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<Object> work = new RunAsWork<Object>()
{
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<Object>()
{
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<TempOutputStream> 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<TempOutputStream> 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);
}
}

View File

@@ -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. <code>isDir == true</code>, 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<TempOutputStream> factory(final File tempDir, final int memoryThreshold,
final long maxContentSize, final boolean encrypt)
{
return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.web.scripts;
import java.io.File;
/**
* Factory for {@link TempOutputStream}
*/
public class TempOutputStreamFactory
{
/**
* A temporary directory, i.e. <code>isDir == true</code>, 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. <code>isDir == true</code>, 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;
}
}

View File

@@ -23,149 +23,144 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #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);
}
}
}

View File

@@ -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);

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -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

View File

@@ -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<ModulePackage> getModules()
@@ -194,4 +211,10 @@ public class DiscoveryApiWebscript extends AbstractWebScript implements Recogniz
throw new DisabledServiceException(DISABLED);
}
}
protected boolean isContentDirectUrlEnabled()
{
return (restApiDirectUrlConfig.isEnabled() && contentService.isContentDirectUrlEnabled());
}
}

View File

@@ -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

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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<Node>, 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;
}
}

View File

@@ -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<TempOutputStream> 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<TempOutputStream> 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<String, String> templateVars = req.getServiceMatch().getTemplateVars();
Api api = assistant.determineApi(templateVars);
final BufferedRequest bufferedReq = getRequest(req);
final BufferedResponse bufferedRes = getResponse(res);
final Map<String, String> 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;

View File

@@ -23,110 +23,110 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #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.
* <p>
* 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<Object> values = new ArrayList<Object>();
for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts())
{
if (family != null)
{
Set<String> 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<StoreRef> 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.
* <p>
* 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<Object> values = new ArrayList<Object>();
for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts())
{
if (family != null)
{
Set<String> 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<StoreRef> getStores()
{
return this.services.getNodeService().getStores();
}
}

View File

@@ -942,8 +942,13 @@
<property name="sites" ref="Sites" />
</bean>
<bean id= "directAccessUrlHelper" class="org.alfresco.rest.api.DirectAccessUrlHelper">
<property name="restApiDirectUrlConfig" ref="restApiDirectUrlConfig" />
</bean>
<bean class="org.alfresco.rest.api.nodes.NodesEntityResource">
<property name="nodes" ref="Nodes" />
<property name="directAccessUrlHelper" ref="directAccessUrlHelper" />
</bean>
<bean class="org.alfresco.rest.api.nodes.NodeCommentsRelation">
@@ -1063,12 +1068,21 @@
<property name="assistant" ref="apiAssistant" />
<property name="enabled" value="${system.api.discovery.enabled}" />
<property name="thumbnailService" ref="ThumbnailService" />
<property name="restApiDirectUrlConfig" ref="restApiDirectUrlConfig" />
<property name="contentService" ref="ContentService" />
</bean>
<bean id="org.alfresco.rest.api.probes.ProbeEntityResource.get" class="org.alfresco.rest.api.probes.ProbeEntityResource">
<property name="discovery" ref="webscript.org.alfresco.api.DiscoveryApiWebscript.get" />
</bean>
<!-- REST API direct access URL configuration settings -->
<bean id="restApiDirectUrlConfig" class="org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig" init-method="init">
<property name="systemWideDirectUrlConfig" ref="systemWideDirectUrlConfig" />
<property name="enabled" value="${restApi.directAccessUrl.enabled}" />
<property name="defaultExpiryTimeInSec" value="${restApi.directAccessUrl.defaultExpiryTimeInSec}" />
</bean>
<!-- OpenCMIS -->
<bean id="publicApiCMISServiceFactory" class="org.alfresco.opencmis.PublicApiAlfrescoCmisServiceFactory" init-method="init">

View File

@@ -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,

View File

@@ -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
{

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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());
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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
*

View File

@@ -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<TempOutputStream> 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

View File

@@ -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<ModulePackage> modulePackageList = repositoryInfo.getModules();

View File

@@ -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);
}
}

View File

@@ -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<TempOutputStream> 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<TempOutputStream> 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<TempOutputStream> 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<TempOutputStream> 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<TempOutputStream> 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();

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>11.95-SNAPSHOT</version>
<version>11.105-SNAPSHOT</version>
</parent>
<dependencies>

View File

@@ -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;
}
}

View File

@@ -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}
* <p>
* 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}
}
}

View File

@@ -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<ContentStore> secondaryStores;
@@ -266,33 +266,58 @@ public class AggregatingContentStore extends AbstractContentStore
}
/**
* @return Returns <tt>true</tt> 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)
{

View File

@@ -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.
* <p>
@@ -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);
}

View File

@@ -1,359 +1,376 @@
<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- ContentStore subsystem that allows switching between different types e.g. unencrypted and encrypted -->
<bean id="ContentStore" class="org.alfresco.repo.management.subsystems.CryptodocSwitchableApplicationContextFactory"
parent="abstractPropertyBackedBean">
<property name="autoStart">
<value>false</value>
</property>
<property name="category">
<value>ContentStore</value>
</property>
<property name="sourceBeanName">
<!-- default subsystem's bean name -->
<value>${filecontentstore.subsystem.name}</value>
</property>
<property name="instancePath">
<list>
<value>manager</value>
</list>
</property>
</bean>
<!-- Default ContentStore subsystem, that does not use encryption -->
<bean id="unencryptedContentStore" class="org.alfresco.repo.management.subsystems.ChildApplicationContextFactory" parent="abstractPropertyBackedBean">
<property name="autoStart">
<value>true</value>
</property>
<property name="category">
<value>ContentStore</value>
</property>
<property name="typeName">
<value>unencrypted</value>
</property>
<property name="instancePath">
<list>
<value>managed</value>
<value>unencrypted</value>
</list>
</property>
</bean>
<!-- Import the selected ContentStore subsystem's fileContentStore bean for use in the main repository context -->
<bean id="fileContentStore" class="org.alfresco.repo.management.subsystems.CryptodocSubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="ContentStore" />
</property>
<property name="sourceBeanName">
<value>fileContentStore</value>
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.content.ContentStore</value>
<value>org.alfresco.repo.content.ContentStoreCaps</value>
</list>
</property>
</bean>
<bean id="defaultFileContentUrlProvider" class="org.alfresco.repo.content.filestore.TimeBasedFileContentUrlProvider">
<property name="bucketsPerMinute" value="${dir.contentstore.bucketsPerMinute}"/>
</bean>
<!-- This content limit provider is used above (and can also be overriden, eg. by modules). -->
<bean id="defaultContentLimitProvider" class="org.alfresco.repo.content.ContentLimitProvider$SimpleFixedLimitProvider">
<property name="sizeLimitString" value="${system.content.maximumFileSizeLimit}"/>
</bean>
<!-- deleted content will get pushed into this store, where it can be cleaned up at will -->
<bean id="deletedContentStore" class="org.alfresco.repo.content.filestore.FileContentStore">
<constructor-arg>
<value>${dir.contentstore.deleted}</value>
</constructor-arg>
</bean>
<!-- bean to move deleted content into the the backup store -->
<bean id="deletedContentBackupListener" class="org.alfresco.repo.content.cleanup.DeletedContentBackupCleanerListener" >
<property name="store">
<ref bean="deletedContentStore" />
</property>
</bean>
<!-- A list of content deletion listeners. This is split out for re-use. -->
<bean id="deletedContentBackupListeners" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="deletedContentBackupListener" />
</list>
</constructor-arg>
</bean>
<!-- Performs the content cleanup -->
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" init-method="init">
<property name="protectDays" >
<value>${system.content.orphanProtectDays}</value>
</property>
<property name="deletionFailureAction" >
<value>${system.content.deletionFailureAction}</value>
</property>
<property name="eagerContentStoreCleaner" >
<ref bean="eagerContentStoreCleaner" />
</property>
<property name="jobLockService">
<ref bean="jobLockService" />
</property>
<property name="contentDataDAO">
<ref bean="contentDataDAO"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="contentService" >
<ref bean="contentService" />
</property>
<property name="transactionService" >
<ref bean="transactionService" />
</property>
<property name="batchSize" >
<value>${system.content.cleanerBatchSize}</value>
</property>
</bean>
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
<property name="eagerOrphanCleanup" >
<value>${system.content.eagerOrphanCleanup}</value>
</property>
<property name="stores" ref="contentStoresToClean" />
<property name="listeners" >
<ref bean="deletedContentBackupListeners" />
</property>
</bean>
<bean id="contentStoresToClean" class="java.util.ArrayList" >
<constructor-arg>
<list>
<ref bean="fileContentStore" />
</list>
</constructor-arg>
</bean>
<!-- Abstract bean definition defining base definition for content service -->
<bean id="baseContentService" class="org.alfresco.repo.content.ContentServiceImpl" abstract="true" init-method="init">
<property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="nodeService">
<ref bean="nodeService" />
</property>
<property name="policyComponent">
<ref bean="policyComponent" />
</property>
<property name="mimetypeService">
<ref bean="mimetypeService"/>
</property>
<property name="eagerContentStoreCleaner" >
<ref bean="eagerContentStoreCleaner" />
</property>
<property name="ignoreEmptyContent" >
<value>${policy.content.update.ignoreEmpty}</value>
</property>
</bean>
<bean id="contentService" parent="baseContentService">
<property name="store">
<ref bean="fileContentStore" />
</property>
</bean>
<!-- Our common Tika configuration -->
<bean id="tikaConfig" class="org.apache.tika.config.TikaConfig" >
<constructor-arg type="java.io.InputStream" value="classpath:alfresco/tika/tika-config.xml" />
</bean>
<!-- Characterset decoder -->
<bean id="charset.finder" class="org.alfresco.repo.content.encoding.ContentCharsetFinder">
<property name="defaultCharset">
<value>UTF-8</value>
</property>
<property name="mimetypeService">
<ref bean="mimetypeService"/>
</property>
<property name="charactersetFinders">
<list>
<bean class="org.alfresco.encoding.GuessEncodingCharsetFinder" />
<bean class="org.alfresco.encoding.TikaCharsetFinder" />
</list>
</property>
</bean>
<bean id="shutdownIndicator" class="org.alfresco.util.ShutdownIndicator"/>
<bean id="mimetypeConfigService" class="org.springframework.extensions.config.xml.XMLConfigService" init-method="init">
<constructor-arg>
<bean class="org.alfresco.util.ResourceFinderConfigSource">
<property name="resourceFinder">
<ref bean="resourceFinder" />
</property>
<property name="locations">
<list>
<value>classpath:alfresco/mimetype/mimetype-map.xml</value>
<value>classpath:alfresco/mimetype/mimetype-map-openoffice.xml</value>
<value>classpath*:alfresco/module/*/mimetype-map*.xml</value>
<value>classpath*:alfresco/extension/mimetype/*-map.xml</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
<bean id="mimetypeService" class="org.alfresco.repo.content.MimetypeMap" init-method="init" >
<property name="configService">
<ref bean="mimetypeConfigService" />
</property>
<property name="contentCharsetFinder">
<ref bean="charset.finder"/>
</property>
<property name="tikaConfig">
<ref bean="tikaConfig"/>
</property>
<property name="jsonObjectMapper" ref="mimetypeServiceJsonObjectMapper" />
<property name="mimetypeJsonConfigDir" value="${mimetype.config.dir}" />
<property name="cronExpression" value="${mimetype.config.cronExpression}" />
<property name="initialAndOnErrorCronExpression" value="${mimetype.config.initialAndOnError.cronExpression}" />
<property name="shutdownIndicator" ref="shutdownIndicator" />
</bean>
<bean id="mimetypeServiceJsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
<bean id="contentFilterLanguagesConfigService" class="org.springframework.extensions.config.xml.XMLConfigService" init-method="init">
<constructor-arg>
<bean class="org.springframework.extensions.config.source.UrlConfigSource">
<constructor-arg>
<list>
<value>classpath:alfresco/ml/content-filter-lang.xml</value>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="contentFilterLanguagesService" class="org.alfresco.repo.model.ml.ContentFilterLanguagesMap" init-method="init" >
<property name="configService">
<ref bean="contentFilterLanguagesConfigService" />
</property>
</bean>
<!-- Metadata Extraction Registry -->
<bean id="metadataExtracterRegistry" class="org.alfresco.repo.content.metadata.MetadataExtracterRegistry">
<property name="asyncExtractEnabled" value="${content.metadata.async.extract.enabled}" />
<property name="asyncEmbedEnabled" value="${content.metadata.async.embed.enabled}" />
</bean>
<!-- Abstract bean definition defining base definition for all metadata extracters -->
<bean id="baseMetadataExtracter"
abstract="true"
init-method="register">
<property name="registry">
<ref bean="metadataExtracterRegistry" />
</property>
<property name="mimetypeService">
<ref bean="mimetypeService" />
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="properties">
<ref bean="global-properties" />
</property>
<property name="supportedDateFormats">
<list>
<value>EEE, d MMM yyyy HH:mm:ss Z</value>
<value>EEE, d MMM yy HH:mm:ss Z</value>
<value>d MMM yyyy HH:mm:ss Z</value>
</list>
</property>
</bean>
<!-- Content Metadata Extractors -->
<!-- The last one listed for any mimetype will be used if available -->
<bean id="extractor.Asynchronous" class="org.alfresco.repo.content.metadata.AsynchronousExtractor" parent="baseMetadataExtracter">
<property name="nodeService" ref="nodeService" />
<property name="namespacePrefixResolver" ref="namespaceService" />
<property name="transformerDebug" ref="transformerDebug" />
<property name="renditionService2" ref="renditionService2" />
<property name="renditionDefinitionRegistry2" ref="renditionDefinitionRegistry2" />
<property name="contentService" ref="ContentService" />
<property name="transactionService" ref="transactionService" />
<property name="transformServiceRegistry" ref="transformServiceRegistry" />
<property name="taggingService" ref="taggingService" />
<property name="metadataExtractorPropertyMappingOverrides">
<list>
<ref bean="extracter.RFC822" /> <!-- The RM AMP overrides this bean, extending the base class -->
</list>
</property>
</bean>
<!-- No longer used as an extractor but still extended by RM to provide additional mappings -->
<bean id="extracter.RFC822" class="org.alfresco.repo.content.metadata.RFC822MetadataExtracter" parent="baseMetadataExtracter" >
<property name="nodeService" ref="nodeService"/>
</bean>
<!-- Transformation Debug -->
<bean id="transformerDebug" class="org.alfresco.repo.content.transform.AdminUiTransformerDebug">
<property name="nodeService" ref="nodeService" />
<property name="mimetypeService" ref="mimetypeService" />
<property name="transformerLog" ref="transformerLog" />
<property name="transformerDebugLog" ref="transformerDebugLog" />
<property name="localTransformServiceRegistry" ref="localTransformServiceRegistry" />
<property name="remoteTransformServiceRegistry" ref="remoteTransformServiceRegistry" />
</bean>
<!-- Transformer JMX bean (in addition to sub system properties) -->
<bean id="transformerConfigMBean" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.content.transform.TransformerConfigMBean</value>
</list>
</property>
</bean>
<!-- Logger for transformer debug that may be accessed via JMX -->
<bean id="transformerDebugLog" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="sourceBeanName">
<value>transformerDebugLog</value>
</property>
<property name="interfaces">
<list>
<value>org.apache.commons.logging.Log</value>
</list>
</property>
</bean>
<!-- Logger for transformer log that may be accessed via JMX -->
<bean id="transformerLog" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="sourceBeanName">
<value>transformerLog</value>
</property>
<property name="interfaces">
<list>
<value>org.apache.commons.logging.Log</value>
</list>
</property>
</bean>
</beans>
<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- ContentStore subsystem that allows switching between different types e.g. unencrypted and encrypted -->
<bean id="ContentStore" class="org.alfresco.repo.management.subsystems.CryptodocSwitchableApplicationContextFactory"
parent="abstractPropertyBackedBean">
<property name="autoStart">
<value>false</value>
</property>
<property name="category">
<value>ContentStore</value>
</property>
<property name="sourceBeanName">
<!-- default subsystem's bean name -->
<value>${filecontentstore.subsystem.name}</value>
</property>
<property name="instancePath">
<list>
<value>manager</value>
</list>
</property>
</bean>
<!-- Default ContentStore subsystem, that does not use encryption -->
<bean id="unencryptedContentStore" class="org.alfresco.repo.management.subsystems.ChildApplicationContextFactory" parent="abstractPropertyBackedBean">
<property name="autoStart">
<value>true</value>
</property>
<property name="category">
<value>ContentStore</value>
</property>
<property name="typeName">
<value>unencrypted</value>
</property>
<property name="instancePath">
<list>
<value>managed</value>
<value>unencrypted</value>
</list>
</property>
</bean>
<!-- Import the selected ContentStore subsystem's fileContentStore bean for use in the main repository context -->
<bean id="fileContentStore" class="org.alfresco.repo.management.subsystems.CryptodocSubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="ContentStore" />
</property>
<property name="sourceBeanName">
<value>fileContentStore</value>
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.content.ContentStore</value>
<value>org.alfresco.repo.content.ContentStoreCaps</value>
</list>
</property>
</bean>
<bean id="defaultFileContentUrlProvider" class="org.alfresco.repo.content.filestore.TimeBasedFileContentUrlProvider">
<property name="bucketsPerMinute" value="${dir.contentstore.bucketsPerMinute}"/>
</bean>
<!-- This content limit provider is used above (and can also be overriden, eg. by modules). -->
<bean id="defaultContentLimitProvider" class="org.alfresco.repo.content.ContentLimitProvider$SimpleFixedLimitProvider">
<property name="sizeLimitString" value="${system.content.maximumFileSizeLimit}"/>
</bean>
<!-- deleted content will get pushed into this store, where it can be cleaned up at will -->
<bean id="deletedContentStore" class="org.alfresco.repo.content.filestore.FileContentStore">
<constructor-arg>
<value>${dir.contentstore.deleted}</value>
</constructor-arg>
</bean>
<!-- bean to move deleted content into the the backup store -->
<bean id="deletedContentBackupListener" class="org.alfresco.repo.content.cleanup.DeletedContentBackupCleanerListener" >
<property name="store">
<ref bean="deletedContentStore" />
</property>
</bean>
<!-- A list of content deletion listeners. This is split out for re-use. -->
<bean id="deletedContentBackupListeners" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="deletedContentBackupListener" />
</list>
</constructor-arg>
</bean>
<!-- Performs the content cleanup -->
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" init-method="init">
<property name="protectDays" >
<value>${system.content.orphanProtectDays}</value>
</property>
<property name="deletionFailureAction" >
<value>${system.content.deletionFailureAction}</value>
</property>
<property name="eagerContentStoreCleaner" >
<ref bean="eagerContentStoreCleaner" />
</property>
<property name="jobLockService">
<ref bean="jobLockService" />
</property>
<property name="contentDataDAO">
<ref bean="contentDataDAO"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="contentService" >
<ref bean="contentService" />
</property>
<property name="transactionService" >
<ref bean="transactionService" />
</property>
<property name="batchSize" >
<value>${system.content.cleanerBatchSize}</value>
</property>
</bean>
<bean id="eagerContentStoreCleaner" class="org.alfresco.repo.content.cleanup.EagerContentStoreCleaner" init-method="init">
<property name="eagerOrphanCleanup" >
<value>${system.content.eagerOrphanCleanup}</value>
</property>
<property name="stores" ref="contentStoresToClean" />
<property name="listeners" >
<ref bean="deletedContentBackupListeners" />
</property>
</bean>
<bean id="contentStoresToClean" class="java.util.ArrayList" >
<constructor-arg>
<list>
<ref bean="fileContentStore" />
</list>
</constructor-arg>
</bean>
<!-- Abstract bean definition defining base definition for content service -->
<bean id="baseContentService" class="org.alfresco.repo.content.ContentServiceImpl" abstract="true" init-method="init">
<property name="retryingTransactionHelper">
<ref bean="retryingTransactionHelper"/>
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="nodeService">
<ref bean="nodeService" />
</property>
<property name="policyComponent">
<ref bean="policyComponent" />
</property>
<property name="mimetypeService">
<ref bean="mimetypeService"/>
</property>
<property name="eagerContentStoreCleaner" >
<ref bean="eagerContentStoreCleaner" />
</property>
<property name="ignoreEmptyContent" >
<value>${policy.content.update.ignoreEmpty}</value>
</property>
<property name="systemWideDirectUrlConfig" >
<ref bean="systemWideDirectUrlConfig" />
</property>
</bean>
<bean id="contentService" parent="baseContentService">
<property name="store">
<ref bean="fileContentStore" />
</property>
</bean>
<!-- Our common Tika configuration -->
<bean id="tikaConfig" class="org.apache.tika.config.TikaConfig" >
<constructor-arg type="java.io.InputStream" value="classpath:alfresco/tika/tika-config.xml" />
</bean>
<!-- Characterset decoder -->
<bean id="charset.finder" class="org.alfresco.repo.content.encoding.ContentCharsetFinder">
<property name="defaultCharset">
<value>UTF-8</value>
</property>
<property name="mimetypeService">
<ref bean="mimetypeService"/>
</property>
<property name="charactersetFinders">
<list>
<bean class="org.alfresco.encoding.GuessEncodingCharsetFinder" />
<bean class="org.alfresco.encoding.TikaCharsetFinder" />
</list>
</property>
</bean>
<bean id="shutdownIndicator" class="org.alfresco.util.ShutdownIndicator"/>
<bean id="mimetypeConfigService" class="org.springframework.extensions.config.xml.XMLConfigService" init-method="init">
<constructor-arg>
<bean class="org.alfresco.util.ResourceFinderConfigSource">
<property name="resourceFinder">
<ref bean="resourceFinder" />
</property>
<property name="locations">
<list>
<value>classpath:alfresco/mimetype/mimetype-map.xml</value>
<value>classpath:alfresco/mimetype/mimetype-map-openoffice.xml</value>
<value>classpath*:alfresco/module/*/mimetype-map*.xml</value>
<value>classpath*:alfresco/extension/mimetype/*-map.xml</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
<bean id="mimetypeService" class="org.alfresco.repo.content.MimetypeMap" init-method="init" >
<property name="configService">
<ref bean="mimetypeConfigService" />
</property>
<property name="contentCharsetFinder">
<ref bean="charset.finder"/>
</property>
<property name="tikaConfig">
<ref bean="tikaConfig"/>
</property>
<property name="jsonObjectMapper" ref="mimetypeServiceJsonObjectMapper" />
<property name="mimetypeJsonConfigDir" value="${mimetype.config.dir}" />
<property name="cronExpression" value="${mimetype.config.cronExpression}" />
<property name="initialAndOnErrorCronExpression" value="${mimetype.config.initialAndOnError.cronExpression}" />
<property name="shutdownIndicator" ref="shutdownIndicator" />
</bean>
<bean id="mimetypeServiceJsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
<bean id="contentFilterLanguagesConfigService" class="org.springframework.extensions.config.xml.XMLConfigService" init-method="init">
<constructor-arg>
<bean class="org.springframework.extensions.config.source.UrlConfigSource">
<constructor-arg>
<list>
<value>classpath:alfresco/ml/content-filter-lang.xml</value>
</list>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id="contentFilterLanguagesService" class="org.alfresco.repo.model.ml.ContentFilterLanguagesMap" init-method="init" >
<property name="configService">
<ref bean="contentFilterLanguagesConfigService" />
</property>
</bean>
<!-- Metadata Extraction Registry -->
<bean id="metadataExtracterRegistry" class="org.alfresco.repo.content.metadata.MetadataExtracterRegistry">
<property name="asyncExtractEnabled" value="${content.metadata.async.extract.enabled}" />
<property name="asyncEmbedEnabled" value="${content.metadata.async.embed.enabled}" />
</bean>
<!-- Abstract bean definition defining base definition for all metadata extracters -->
<bean id="baseMetadataExtracter"
abstract="true"
init-method="register">
<property name="registry">
<ref bean="metadataExtracterRegistry" />
</property>
<property name="mimetypeService">
<ref bean="mimetypeService" />
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="properties">
<ref bean="global-properties" />
</property>
<property name="supportedDateFormats">
<list>
<value>EEE, d MMM yyyy HH:mm:ss Z</value>
<value>EEE, d MMM yy HH:mm:ss Z</value>
<value>d MMM yyyy HH:mm:ss Z</value>
</list>
</property>
</bean>
<!-- Content Metadata Extractors -->
<!-- The last one listed for any mimetype will be used if available -->
<bean id="extractor.Asynchronous" class="org.alfresco.repo.content.metadata.AsynchronousExtractor" parent="baseMetadataExtracter">
<property name="nodeService" ref="nodeService" />
<property name="namespacePrefixResolver" ref="namespaceService" />
<property name="transformerDebug" ref="transformerDebug" />
<property name="renditionService2" ref="renditionService2" />
<property name="renditionDefinitionRegistry2" ref="renditionDefinitionRegistry2" />
<property name="contentService" ref="ContentService" />
<property name="transactionService" ref="transactionService" />
<property name="transformServiceRegistry" ref="transformServiceRegistry" />
<property name="taggingService" ref="taggingService" />
<property name="metadataExtractorPropertyMappingOverrides">
<list>
<ref bean="extracter.RFC822" /> <!-- The RM AMP overrides this bean, extending the base class -->
</list>
</property>
</bean>
<!-- No longer used as an extractor but still extended by RM to provide additional mappings -->
<bean id="extracter.RFC822" class="org.alfresco.repo.content.metadata.RFC822MetadataExtracter" parent="baseMetadataExtracter" >
<property name="nodeService" ref="nodeService"/>
</bean>
<!-- Transformation Debug -->
<bean id="transformerDebug" class="org.alfresco.repo.content.transform.AdminUiTransformerDebug">
<property name="nodeService" ref="nodeService" />
<property name="mimetypeService" ref="mimetypeService" />
<property name="transformerLog" ref="transformerLog" />
<property name="transformerDebugLog" ref="transformerDebugLog" />
<property name="localTransformServiceRegistry" ref="localTransformServiceRegistry" />
<property name="remoteTransformServiceRegistry" ref="remoteTransformServiceRegistry" />
</bean>
<!-- Transformer JMX bean (in addition to sub system properties) -->
<bean id="transformerConfigMBean" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.content.transform.TransformerConfigMBean</value>
</list>
</property>
</bean>
<!-- Logger for transformer debug that may be accessed via JMX -->
<bean id="transformerDebugLog" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="sourceBeanName">
<value>transformerDebugLog</value>
</property>
<property name="interfaces">
<list>
<value>org.apache.commons.logging.Log</value>
</list>
</property>
</bean>
<!-- Logger for transformer log that may be accessed via JMX -->
<bean id="transformerLog" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
<property name="sourceApplicationContextFactory">
<ref bean="Transformers" />
</property>
<property name="sourceBeanName">
<value>transformerLog</value>
</property>
<property name="interfaces">
<list>
<value>org.apache.commons.logging.Log</value>
</list>
</property>
</bean>
<!-- System direct access URL configuration settings -->
<bean id="systemWideDirectUrlConfig" class="org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig" init-method="init">
<property name="enabled" value="${system.directAccessUrl.enabled}" />
<property name="defaultExpiryTimeInSec" value="${system.directAccessUrl.defaultExpiryTimeInSec}" />
<property name="maxExpiryTimeInSec" value="${system.directAccessUrl.maxExpiryTimeInSec}" />
</bean>
<!-- Abstract Direct Access URL configuration -->
<bean id="abstractDirectUrlConfig" class="org.alfresco.repo.content.directurl.AbstractDirectUrlConfig" abstract="true">
<property name="systemWideDirectUrlConfig" >
<ref bean="systemWideDirectUrlConfig" />
</property>
</bean>
</beans>

View File

@@ -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
</value>
</property>

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}