diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index 9b8f5ee2e1..78c62c3a3b 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -24,7 +24,7 @@ - nightlybuilds + releases 1.8 1.8 testng.xml diff --git a/rm-automation/rm-automation-community-rest-api/pom.xml b/rm-automation/rm-automation-community-rest-api/pom.xml index 4b176d02ee..0579d444e2 100644 --- a/rm-automation/rm-automation-community-rest-api/pom.xml +++ b/rm-automation/rm-automation-community-rest-api/pom.xml @@ -33,6 +33,11 @@ + + com.jayway.restassured + rest-assured + 2.9.0 + org.alfresco.tas restapi-test diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponent.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponent.java index ff010b222e..d821768829 100644 --- a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponent.java +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponent.java @@ -94,6 +94,9 @@ public class FilePlanComponent @JsonProperty (value = ALLOWABLE_OPERATIONS) private List allowableOperations; + @JsonProperty (required = false) + private FilePlanComponentContent content; + private FilePlanComponentPath path; @JsonProperty (required = true) @@ -104,4 +107,5 @@ public class FilePlanComponent @JsonProperty (required = true) private FilePlanComponentUserInfo modifiedByUser; + } diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentContent.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentContent.java new file mode 100644 index 0000000000..eb2f102266 --- /dev/null +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentContent.java @@ -0,0 +1,113 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.model.fileplancomponents; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * POJO for FilePlanComponent content field + * @author Kristijan Conkas + * @since 2.6 + */ +public class FilePlanComponentContent +{ + @JsonProperty (required = true) + private String encoding; + + @JsonProperty (required = true) + private String mimeType; + + @JsonProperty (required = true) + private String mimeTypeName; + + @JsonProperty (required = true) + private Integer sizeInBytes; + + /** + * @return the encoding + */ + public String getEncoding() + { + return this.encoding; + } + + /** + * @param encoding the encoding to set + */ + public void setEncoding(String encoding) + { + this.encoding = encoding; + } + + /** + * @return the mimeType + */ + public String getMimeType() + { + return this.mimeType; + } + + /** + * @param mimeType the mimeType to set + */ + public void setMimeType(String mimeType) + { + this.mimeType = mimeType; + } + + /** + * @return the mimeTypeName + */ + public String getMimeTypeName() + { + return this.mimeTypeName; + } + + /** + * @param mimeTypeName the mimeTypeName to set + */ + public void setMimeTypeName(String mimeTypeName) + { + this.mimeTypeName = mimeTypeName; + } + + /** + * @return the sizeInBytes + */ + public Integer getSizeInBytes() + { + return this.sizeInBytes; + } + + /** + * @param sizeInBytes the sizeInBytes to set + */ + public void setSizeInBytes(Integer sizeInBytes) + { + this.sizeInBytes = sizeInBytes; + } +} diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java index 98f38afd36..11b14c1906 100644 --- a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java @@ -45,7 +45,17 @@ public class FilePlanComponentFields public static final String PROPERTIES_DESCRIPTION = "cm:description"; public static final String PROPERTIES_SUPPLEMENTAL_MARKING_LIST = "rmc:supplementalMarkingList"; public static final String ALLOWABLE_OPERATIONS = "allowableOperations"; - public static final String IS_CLOSED="isClosed"; - public static final String PROPERTIES_REVIEW_PERIOD="rma:reviewPeriod"; - public static final String PROPERTIES_LOCATION="rma:location"; + public static final String IS_CLOSED = "isClosed"; + public static final String PROPERTIES_REVIEW_PERIOD = "rma:reviewPeriod"; + public static final String PROPERTIES_LOCATION = "rma:location"; + public static final String PROPERTIES_IS_CLOSED = "rma:isClosed"; // not to be confused with IS_CLOSED! + + // for non-electronic records + public static final String PROPERTIES_BOX = "rma:box"; + public static final String PROPERTIES_FILE = "rma:file"; + public static final String PROPERTIES_NUMBER_OF_COPIES = "rma:numberOfCopies"; + public static final String PROPERTIES_PHYSICAL_SIZE = "rma:physicalSize"; + public static final String PROPERTIES_SHELF = "rma:shelf"; + public static final String PROPERTIES_STORAGE_LOCATION = "rma:storageLocation"; + } diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentProperties.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentProperties.java index 84d9efa07f..68b73c0cb3 100644 --- a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentProperties.java +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentProperties.java @@ -26,10 +26,16 @@ */ package org.alfresco.rest.rm.community.model.fileplancomponents; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_BOX; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_DESCRIPTION; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_FILE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_HOLD_REASON; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_IS_CLOSED; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_LOCATION; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_NUMBER_OF_COPIES; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_PHYSICAL_SIZE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_REVIEW_PERIOD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_SHELF; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_SUPPLEMENTAL_MARKING_LIST; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_TITLE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentFields.PROPERTIES_VITAL_RECORD_INDICATOR; @@ -83,4 +89,25 @@ public class FilePlanComponentProperties @JsonProperty(PROPERTIES_LOCATION) private String location; + + + @JsonProperty(value = PROPERTIES_IS_CLOSED, required = false) + private Boolean isClosed; + + @JsonProperty(value = PROPERTIES_BOX, required = false) + private String box; + + @JsonProperty(value = PROPERTIES_FILE, required = false) + private String file; + + @JsonProperty(value = PROPERTIES_SHELF, required = false) + private String shelf; + + @JsonProperty(value = PROPERTIES_NUMBER_OF_COPIES, required = false) + private Integer numberOfCopies; + + @JsonProperty(value = PROPERTIES_PHYSICAL_SIZE, required = false) + private Integer physicalSize; + + } diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentType.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentType.java index 1308a68295..4398087e56 100644 --- a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentType.java +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentType.java @@ -46,7 +46,8 @@ public enum FilePlanComponentType TRANSFER_CONTAINER_TYPE("rma:transferContainer"), UNFILED_CONTAINER_TYPE("rma:unfiledRecordContainer"), FOLDER_TYPE("cm:folder"), - CONTENT_TYPE("cm:content"); + CONTENT_TYPE("cm:content"), + NON_ELECTRONIC_RECORD_TYPE("rma:nonElectronicDocument"); private String type; diff --git a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/FilePlanComponentAPI.java b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/FilePlanComponentAPI.java index 0ba49263ab..9d8c385a91 100644 --- a/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/FilePlanComponentAPI.java +++ b/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/FilePlanComponentAPI.java @@ -26,6 +26,8 @@ */ package org.alfresco.rest.rm.community.requests; +import static com.jayway.restassured.RestAssured.given; + import static org.alfresco.rest.core.RestRequest.requestWithBody; import static org.alfresco.rest.core.RestRequest.simpleRequest; import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryObject; @@ -35,10 +37,19 @@ import static org.springframework.http.HttpMethod.DELETE; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.HttpMethod.PUT; +import static org.testng.Assert.fail; + +import java.io.File; + +import com.google.common.io.Resources; +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.response.Response; import org.alfresco.rest.core.RestAPI; import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponent; +import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType; import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentsCollection; +import org.alfresco.utility.model.UserModel; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -119,13 +130,62 @@ public class FilePlanComponentAPI extends RestAPI { mandatoryObject("filePlanComponentProperties", filePlanComponentModel); mandatoryString("parentId", parentId); - + return usingRestWrapper().processModel(FilePlanComponent.class, requestWithBody( - POST, - toJson(filePlanComponentModel), - "fileplan-components/{fileplanComponentId}/children", - parentId - )); + POST, + toJson(filePlanComponentModel), + "fileplan-components/{fileplanComponentId}/children?{parameters}", + parentId, + getParameters())); + } + + /** + * Create electronic record from file resource + * @param electronicRecordModel {@link FilePlanComponent} for electronic record to be created + * @param fileName the name of the resource file + * @param parentId parent container id + * @return newly created {@link FilePlanComponent} + * @throws Exception if operation failed + */ + public FilePlanComponent createElectronicRecord(FilePlanComponent electronicRecordModel, String fileName, String parentId) throws Exception + { + return createElectronicRecord(electronicRecordModel, new File(Resources.getResource(fileName).getFile()), parentId); + } + + /** + * Create electronic record from file resource + * @param electronicRecordModel {@link FilePlanComponent} for electronic record to be created + * @param recordContent {@link File} pointing to the content of the electronic record to be created + * @param parentId parent container id + * @return newly created {@link FilePlanComponent} + * @throws Exception if operation failed + */ + public FilePlanComponent createElectronicRecord(FilePlanComponent electronicRecordModel, File recordContent, String parentId) throws Exception + { + mandatoryObject("filePlanComponentProperties", electronicRecordModel); + mandatoryString("parentId", parentId); + if (!electronicRecordModel.getNodeType().equals(FilePlanComponentType.CONTENT_TYPE.toString())) + { + fail("Only electronic records are supported"); + } + + /* + * RestWrapper adds some headers which break multipart/form-data uploads and also assumes json POST requests. + * Upload the file using RestAssured library. + */ + UserModel currentUser = usingRestWrapper().getTestUser(); + Response response = given() + .auth().basic(currentUser.getUsername(), currentUser.getPassword()) + .multiPart("nodeBodyCreate", toJson(electronicRecordModel), ContentType.JSON.name()) + .multiPart("filedata", recordContent, ContentType.BINARY.name()) + .when() + .post("fileplan-components/{fileplanComponentId}/children?{parameters}", parentId, getParameters()) + .andReturn(); + usingRestWrapper().setStatusCode(Integer.toString(response.getStatusCode())); + LOG.info("electronic record created: " + response.getBody().prettyPrint()); + + /* return a FilePlanComponent object representing Response */ + return response.jsonPath().getObject("entry", FilePlanComponent.class); } /** @@ -152,8 +212,9 @@ public class FilePlanComponentAPI extends RestAPI return usingRestWrapper().processModel(FilePlanComponent.class, requestWithBody( PUT, toJson(filePlanComponent), - "fileplan-components/{fileplanComponentId}", - filePlanComponentId + "fileplan-components/{fileplanComponentId}?{parameters}", + filePlanComponentId, + getParameters() )); } diff --git a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRestTest.java b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRestTest.java index 71b590dbb6..bd445a05df 100644 --- a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRestTest.java +++ b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/BaseRestTest.java @@ -30,11 +30,15 @@ import static java.lang.Integer.parseInt; import static org.alfresco.rest.rm.community.base.TestData.CATEGORY_TITLE; import static org.alfresco.rest.rm.community.base.TestData.FOLDER_TITLE; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_CATEGORY_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_FOLDER_TYPE; import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE; import static org.alfresco.rest.rm.community.model.site.RMSiteCompliance.STANDARD; +import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.OK; import com.jayway.restassured.RestAssured; @@ -47,11 +51,13 @@ import org.alfresco.rest.rm.community.model.site.RMSite; import org.alfresco.rest.rm.community.requests.FilePlanComponentAPI; import org.alfresco.rest.rm.community.requests.RMSiteAPI; import org.alfresco.utility.data.DataUser; +import org.alfresco.utility.model.UserModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; /** * Base class for all IG REST API Tests @@ -83,7 +89,7 @@ public class BaseRestTest extends RestTest @Autowired private RMSiteAPI rmSiteAPI; - + @Autowired private DataUser dataUser; @@ -95,6 +101,19 @@ public class BaseRestTest extends RestTest public static final String RM_TITLE = "Records Management"; public static final String RM_DESCRIPTION = "Records Management Site"; + /** Valid root containers where electronic and non-electronic records can be created */ + @DataProvider(name = "validRootContainers") + public Object[][] getValidRootContainers() throws Exception { + return new Object[][] { + // an arbitrary record folder + { createCategoryFolderInFilePlan(dataUser.getAdminUser(), FILE_PLAN_ALIAS.toString()) }, + // unfiled records root + { getFilePlanComponentAsUser(dataUser.getAdminUser(), UNFILED_RECORDS_CONTAINER_ALIAS.toString()) }, + // an arbitrary unfiled records folder + { createUnfiledRecordsFolder(UNFILED_RECORDS_CONTAINER_ALIAS.toString(), "Unfiled Folder " + getRandomAlphanumeric()) } + }; + } + /** * @see org.alfresco.rest.RestTest#checkServerHealth() */ @@ -196,4 +215,55 @@ public class BaseRestTest extends RestTest return fpc; } + /** + * Helper method to close folder + * @param folderToClose + * @return + * @throws Exception + */ + public FilePlanComponent closeFolder(String folderId) throws Exception + { + RestWrapper restWrapper = filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + // build fileplan component + properties for update request + FilePlanComponentProperties properties = new FilePlanComponentProperties(); + properties.setIsClosed(true); + FilePlanComponent filePlanComponent = new FilePlanComponent(); + filePlanComponent.setProperties(properties); + + FilePlanComponent updatedComponent = filePlanComponentAPI.updateFilePlanComponent(filePlanComponent, folderId); + restWrapper.assertStatusCodeIs(OK); + return updatedComponent; + } + + /** + * Helper method to create a randomly-named / structure in fileplan + * @param user user under whose privileges this structure is going to be created + * @param parentId parent container id + * @return record folder + * @throws Exception on failed creation + */ + public FilePlanComponent createCategoryFolderInFilePlan(UserModel user, String parentId) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(user); + + // create root category + FilePlanComponent recordCategory = createCategory(parentId, "Category " + getRandomAlphanumeric()); + + // and return a folder underneath + return createFolder(recordCategory.getId(), "Folder " + getRandomAlphanumeric()); + } + + /** + * Helper method to retieve a fileplan component with user's privilege + * @param user user under whose privileges a component is to be read + * @param componentId id of the component to read + * @return {@link FilePlanComponent} for given componentId + * @throws Exception if user doesn't have sufficient privileges + */ + public FilePlanComponent getFilePlanComponentAsUser(UserModel user, String componentId) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(user); + return filePlanComponentAPI.getFilePlanComponent(componentId); + } } \ No newline at end of file diff --git a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/TestData.java b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/TestData.java index 43c9147d5f..ee890dfab9 100644 --- a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/TestData.java +++ b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/base/TestData.java @@ -50,7 +50,7 @@ import org.testng.annotations.DataProvider; * @since 2.6 */ public interface TestData -{ +{ /** * A user with ALFRESCO_ADMINISTRATORS role. *

"GROUP_ANOTHER_ADMIN_EXISTS" The ANOTHER_ADMIN user has been created. @@ -141,5 +141,4 @@ public interface TestData { CONTENT_TYPE.toString()} }; } - } diff --git a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/ElectronicRecordTests.java b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/ElectronicRecordTests.java new file mode 100644 index 0000000000..2b30770dc8 --- /dev/null +++ b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/ElectronicRecordTests.java @@ -0,0 +1,232 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.fileplancomponents; + +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.HOLDS_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.TRANSFERS_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.CONTENT_TYPE; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_FOLDER_TYPE; +import static org.alfresco.rest.rm.community.util.PojoUtility.toJson; +import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import org.alfresco.rest.rm.community.base.BaseRestTest; +import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponent; +import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentProperties; +import org.alfresco.rest.rm.community.requests.FilePlanComponentAPI; +import org.alfresco.utility.data.DataUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Create/File electronic records tests + *
+ * These tests only test the creation and filing of electronic records, update at + * present isn't implemented in the API under test. + *

+ * @author Kristijan Conkas + * @since 2.6 + */ +public class ElectronicRecordTests extends BaseRestTest +{ + @Autowired + private FilePlanComponentAPI filePlanComponentAPI; + + @Autowired + private DataUser dataUser; + + /** image resource file to be used for records body */ + private static final String IMAGE_FILE = "money.JPG"; + + /** Valid root containers where electronic records can be created */ + @DataProvider(name = "invalidParentContainers") + public Object[][] invalidContainers() throws Exception { + return new Object[][] { + // record category + { getFilePlanComponentAsUser(dataUser.getAdminUser(), + createCategoryFolderInFilePlan(dataUser.getAdminUser(), FILE_PLAN_ALIAS.toString()).getParentId()) }, + // file plan root + { getFilePlanComponentAsUser(dataUser.getAdminUser(), FILE_PLAN_ALIAS.toString()) }, + // transfers + { getFilePlanComponentAsUser(dataUser.getAdminUser(), TRANSFERS_ALIAS.toString()) }, + // holds + { getFilePlanComponentAsUser(dataUser.getAdminUser(), HOLDS_ALIAS.toString()) }, + }; + } + + /** + *

+     * Given a parent container that is NOT a record folder or an unfiled record folder
+     * When I try to create an electronic record within the parent container
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @param container + * @throws Exception + */ + @Test + ( + dataProvider = "invalidParentContainers", + description = "Electronic records can't be created in invalid parent containers" + ) + public void cantCreateElectronicRecordsInInvalidContainers(FilePlanComponent container) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + FilePlanComponent record = new FilePlanComponent("Record " + getRandomAlphanumeric(), CONTENT_TYPE.toString(), + new FilePlanComponentProperties()); + filePlanComponentAPI.createElectronicRecord(record, IMAGE_FILE, container.getId()); + + // verify the create request status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(UNPROCESSABLE_ENTITY); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is closed 
+     * When I try to create an electronic record within the parent container
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @throws Exception + */ + @Test(description = "Electronic record can't be created in closed record folder") + public void cantCreateElectronicRecordInClosedFolder() throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + FilePlanComponent recordFolder = createCategoryFolderInFilePlan(dataUser.getAdminUser(), FILE_PLAN_ALIAS.toString()); + + // the folder should be open + assertFalse(recordFolder.getProperties().getIsClosed()); + + // close the folder + closeFolder(recordFolder.getId()); + + // try to create it, this should fail + FilePlanComponent record = new FilePlanComponent("Record " + getRandomAlphanumeric(), CONTENT_TYPE.toString(), + new FilePlanComponentProperties()); + filePlanComponentAPI.createElectronicRecord(record, IMAGE_FILE, recordFolder.getId()); + + // verify the status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(UNPROCESSABLE_ENTITY); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is open
+     * When I try to create an electronic record within the parent container
+     * And I do not provide all the required mandatory property values
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * and + *
+     * Given a parent container that is an unfiled record folder or the root unfiled record container
+     * When I try to create an electronic record within the parent container
+     * And I do not provide all the required mandatory property values
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @param container + * @throws Exception + */ + @Test + ( + dataProvider = "validRootContainers", + description = "Electronic record can only be created if all mandatory properties are given" + ) + public void canCreateElectronicRecordOnlyWithMandatoryProperties(FilePlanComponent container) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + logger.info("Root container:\n" + toJson(container)); + if (container.getNodeType().equals(RECORD_FOLDER_TYPE.toString())) + { + // only record folders can be open or closed + assertFalse(container.getProperties().getIsClosed()); + } + + // component without name + FilePlanComponent record = new FilePlanComponent(); + record.setNodeType(CONTENT_TYPE.toString()); + record.setProperties(new FilePlanComponentProperties()); + + // try to create it + filePlanComponentAPI.createFilePlanComponent(record, container.getId()); + + // verify the status code is BAD_REQUEST + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(BAD_REQUEST); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is open
+     * When I try to create an electronic record within the parent container
+     * Then the electronic record is created
+     * And the details of the new record are returned
+     * 
+ * and + *
+     * Given a parent container that is an unfiled record folder or the root unfiled record container
+     * When I try to create an electronic record within the parent container
+     * Then the electronic record is created
+     * And the details of the new record are returned
+     * 
+ * @throws Exception + */ + @Test + ( + dataProvider = "validRootContainers", + description = "Electronic records can be created in unfiled record folder or unfiled record root" + ) + public void canCreateElectronicRecordsInValidContainers(FilePlanComponent container) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + FilePlanComponent record = new FilePlanComponent("Record " + getRandomAlphanumeric(), CONTENT_TYPE.toString(), + new FilePlanComponentProperties()); + String newRecordId = filePlanComponentAPI.createElectronicRecord(record, IMAGE_FILE, container.getId()).getId(); + + // verify the create request status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(CREATED); + + // get newly created electonic record and verify its properties + FilePlanComponent electronicRecord = filePlanComponentAPI.getFilePlanComponent(newRecordId); + // record will have record identifier inserted in its name but will for sure start with file name + // and end with its extension + assertTrue(electronicRecord.getName().startsWith(IMAGE_FILE.substring(0, IMAGE_FILE.indexOf(".")))); + } +} diff --git a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/NonElectronicRecordTests.java b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/NonElectronicRecordTests.java new file mode 100644 index 0000000000..c8cc153545 --- /dev/null +++ b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/NonElectronicRecordTests.java @@ -0,0 +1,384 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.rm.community.fileplancomponents; + +import static java.util.Arrays.asList; + +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.HOLDS_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.TRANSFERS_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.NON_ELECTRONIC_RECORD_TYPE; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_CATEGORY_TYPE; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.RECORD_FOLDER_TYPE; +import static org.alfresco.rest.rm.community.util.PojoUtility.toJson; +import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + +import java.util.Random; + +import org.alfresco.rest.rm.community.base.BaseRestTest; +import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponent; +import org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentProperties; +import org.alfresco.rest.rm.community.requests.FilePlanComponentAPI; +import org.alfresco.rest.rm.community.requests.RMSiteAPI; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.data.DataUser; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.UserModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.Test; + +/** + * Create/File Non-Electronic Record into Unfiled Record Container/Record Folder ReST API tests + * + * @author Kristijan Conkas + * @since 2.6 + */ +public class NonElectronicRecordTests extends BaseRestTest +{ + @Autowired + private FilePlanComponentAPI filePlanComponentAPI; + + @Autowired + private DataUser dataUser; + + @Autowired + private RMSiteAPI rmSiteAPI; + + /** + *
+     * Given a parent container that is NOT a record folder or an unfiled record folder
+     * When I try to create a non-electronic record within the parent container
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @throws Exception if prerequisites can't be created + */ + @Test(description = "Non-electronic record can't be created as a child of invalid parent Id") + public void cantCreateForInvalidParentIds() throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + // non-electronic record object to be used for create tests + FilePlanComponent nonElectronicRecord = new FilePlanComponent( + "Record " + getRandomAlphanumeric(), + NON_ELECTRONIC_RECORD_TYPE.toString(), + new FilePlanComponentProperties()); + + // create record category, non-electronic records can't be its children + FilePlanComponent recordCategory = filePlanComponentAPI.createFilePlanComponent( + new FilePlanComponent("Category " + getRandomAlphanumeric(), + RECORD_CATEGORY_TYPE.toString(), + new FilePlanComponentProperties()), + FILE_PLAN_ALIAS.toString()); + + // iterate through all invalid parent containers and try to create/file an electronic record + asList(FILE_PLAN_ALIAS.toString(), TRANSFERS_ALIAS.toString(), HOLDS_ALIAS.toString(), recordCategory.getId()) + .stream() + .forEach(id -> + { + try + { + filePlanComponentAPI.createFilePlanComponent(nonElectronicRecord, id); + } + catch (Exception error) + { + } + + // Verify the status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(UNPROCESSABLE_ENTITY); + }); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is open
+     * When I try to create a non-electronic record within the parent container
+     * Then the non-electronic record is created
+     * And the details of the new record are returned
+     * 
+     * and
+     * 
+     * Given a parent container that is an unfiled record folder or the root unfiled record container
+     * When I try to create a non-electronic record within the parent container
+     * Then the non-electronic record is created
+     * And the details of the new record are returned
+     * 
+ * @throws Exception if record can't be created + */ + @Test + ( + dataProvider = "validRootContainers", + description = "Non-electronic records can be created in valid containers" + ) + public void canCreateInValidContainers(FilePlanComponent container) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + logger.info("Root container:\n" + toJson(container)); + if (container.getNodeType().equals(RECORD_FOLDER_TYPE.toString())) + { + // only record folders can be open or closed + assertFalse(container.getProperties().getIsClosed()); + } + + // use these properties for non-electronic record to be created + String title = "Title " + getRandomAlphanumeric(); + String description = "Description " + getRandomAlphanumeric(); + String box = "Box "+ getRandomAlphanumeric(); + String file = "File " + getRandomAlphanumeric(); + String shelf = "Shelf " + getRandomAlphanumeric(); + String location = "Location " + getRandomAlphanumeric(); + + Random random = new Random(); + Integer copies = random.nextInt(Integer.MAX_VALUE); + Integer size = random.nextInt(Integer.MAX_VALUE); + + // set values of all available properties + FilePlanComponentProperties properties = new FilePlanComponentProperties(title, description); + properties.setBox(box); + properties.setFile(file); + properties.setShelf(shelf); + properties.setLocation(location); + properties.setNumberOfCopies(copies); + properties.setPhysicalSize(size); + + // create non-electronic record + String nonElectronicId = filePlanComponentAPI.createFilePlanComponent( + new FilePlanComponent("Record " + getRandomAlphanumeric(), + NON_ELECTRONIC_RECORD_TYPE.toString(), + properties), + container.getId()).getId(); + + // verify the create request status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(CREATED); + + // get newly created non-electonic record and verify its properties + FilePlanComponent nonElectronicRecord = filePlanComponentAPI.getFilePlanComponent(nonElectronicId); + + assertEquals(title, nonElectronicRecord.getProperties().getTitle()); + assertEquals(description, nonElectronicRecord.getProperties().getDescription()); + assertEquals(box, nonElectronicRecord.getProperties().getBox()); + assertEquals(file, nonElectronicRecord.getProperties().getFile()); + assertEquals(shelf, nonElectronicRecord.getProperties().getShelf()); + assertEquals(location, nonElectronicRecord.getProperties().getLocation()); + assertEquals(copies, nonElectronicRecord.getProperties().getNumberOfCopies()); + assertEquals(size, nonElectronicRecord.getProperties().getPhysicalSize()); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is closed 
+     * When I try to create a non-electronic record within the parent container
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @throws Exception if prerequisites can't be created + */ + @Test(description = "Non-electronic record can't be created in closed record folder") + public void cantCreateInClosedFolder() throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + FilePlanComponent recordFolder = createCategoryFolderInFilePlan(dataUser.getAdminUser(), FILE_PLAN_ALIAS.toString()); + + // the folder should be open + assertFalse(recordFolder.getProperties().getIsClosed()); + + // close the folder + closeFolder(recordFolder.getId()); + + // try to create it, this should fail and throw an exception + + filePlanComponentAPI.createFilePlanComponent( + new FilePlanComponent("Record " + getRandomAlphanumeric(), + NON_ELECTRONIC_RECORD_TYPE.toString(), + new FilePlanComponentProperties()), + recordFolder.getId()).getId(); + + // verify the status code + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(UNPROCESSABLE_ENTITY); + } + + /** + *
+     * Given a parent container that is a record folder
+     * And the record folder is open
+     * When I try to create a non-electronic record within the parent container
+     * And I do not provide all the required mandatory property values
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * and + *
+     * Given a parent container that is an unfiled record folder or the root unfiled record container
+     * When I try to create a non-electronic record within the parent container
+     * And I do not provide all the required mandatory property values
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @throws Exception if prerequisites can't be created + */ + @Test + ( + dataProvider = "validRootContainers", + description = "Non-electronic record can only be created if all mandatory properties are given" + ) + public void allMandatoryPropertiesRequired(FilePlanComponent container) throws Exception + { + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + + logger.info("Root container:\n" + toJson(container)); + if (container.getNodeType().equals(RECORD_FOLDER_TYPE.toString())) + { + // only record folders can be open or closed + assertFalse(container.getProperties().getIsClosed()); + } + + // component without name and title + FilePlanComponent noNameOrTitle = getDummyNonElectronicRecord(); + + // component with title only + FilePlanComponent titleOnly = getDummyNonElectronicRecord(); + FilePlanComponentProperties properties = new FilePlanComponentProperties(); + properties.setTitle("Title " + getRandomAlphanumeric()); + titleOnly.setProperties(properties); + + // try to create invalid components + asList(noNameOrTitle, titleOnly).stream().forEach(c -> + { + try + { + logger.info("Creating non-electronic record with body:\n" + toJson(c)); + } + catch (Exception error) + { + } + + // this should fail and throw an exception + try + { + filePlanComponentAPI.createFilePlanComponent(c, container.getId()); + } + catch (Exception e) + { + } + + // verify the status code is BAD_REQUEST + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(BAD_REQUEST); + }); + } + + /** + *
+     * Given that I am a user without RM privileges
+     * When I try to create a non-electronic record
+     * Then nothing happens
+     * And an error is reported
+     * 
+ * @throws Exception + */ + @Test + ( + dataProvider = "validRootContainers", + description = "Non-electronic record can't be created if user doesn't have RM privileges" + ) + public void cantCreateIfNoRmPrivileges(FilePlanComponent container) throws Exception + { + String username = "zzzuser"; + UserModel user = createUserWithRole(username, UserRole.SiteManager); + + filePlanComponentAPI.usingRestWrapper().authenticateUser(user); + + // try to create a fileplan component + FilePlanComponent record = new FilePlanComponent("Record Name", NON_ELECTRONIC_RECORD_TYPE.toString(), + new FilePlanComponentProperties("Name", "Title")); + + // this should fail and throw an exception + try + { + filePlanComponentAPI.createFilePlanComponent(record, container.getId()); + } + catch (Exception e) + { + } + + // user who isn't an RM site member can't access the container path + filePlanComponentAPI.usingRestWrapper().assertStatusCodeIs(FORBIDDEN); + } + + /** + * Helper function to return an empty FilePlanComponent for non-electronic record + * @return + */ + private FilePlanComponent getDummyNonElectronicRecord() + { + FilePlanComponent component = new FilePlanComponent(); + component.setNodeType(NON_ELECTRONIC_RECORD_TYPE.toString()); + return component; + } + + /** + * Create user with given role and add it to RM site + *
+ * Checks whether the user exists in RM site and creates it if required, with password identical + * to username. Note the role is a Core API role, not an RM role. + *
+ * For already existing users, no site membership or role verification is performed. + *

+ * @param userName username to add + * @param userRole user's role + * @throws Exception + */ + private UserModel createUserWithRole(String userName, UserRole userRole) throws Exception + { + rmSiteAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + String siteId = rmSiteAPI.getSite().getId(); + + // check if user exists + UserModel user = new UserModel(); + user.setUsername(userName); + user.setPassword(userName); + + if (!dataUser.isUserInRepo(userName)) + { + // user doesn't exist, create it + user = dataUser.createUser(userName, userName); + user.setUserRole(userRole); + + dataUser.addUserToSite(user, new SiteModel(siteId), userRole); + } + + return user; + } +} diff --git a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/UnfiledRecordsFolderTests.java b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/UnfiledRecordsFolderTests.java index d1d14d4e19..d008d5fbae 100644 --- a/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/UnfiledRecordsFolderTests.java +++ b/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplancomponents/UnfiledRecordsFolderTests.java @@ -100,7 +100,7 @@ public class UnfiledRecordsFolderTests extends BaseRestTest public void createRootUnfiledRecordsFolder() throws Exception { // Authenticate with admin user - RestWrapper restWrapper = filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); String folderName = "Folder " + getRandomAlphanumeric(); String folderTitle = folderName + " Title"; @@ -148,7 +148,7 @@ public class UnfiledRecordsFolderTests extends BaseRestTest ) public void onlyRecordFoldersCanBeCreatedAtUnfiledRecordsRoot(FilePlanComponentType componentType) { - RestWrapper restWrapper = filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); + filePlanComponentAPI.usingRestWrapper().authenticateUser(dataUser.getAdminUser()); String folderName = "Folder " + getRandomAlphanumeric(); String folderTitle = folderName + " Title"; diff --git a/rm-automation/rm-automation-community-rest-api/src/test/resources/money.JPG b/rm-automation/rm-automation-community-rest-api/src/test/resources/money.JPG new file mode 100644 index 0000000000..5939b17709 Binary files /dev/null and b/rm-automation/rm-automation-community-rest-api/src/test/resources/money.JPG differ diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index 70bec9a312..29e46aefb2 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -292,10 +292,16 @@ d:int false - true - false - false - + true + false + false + + + + 0 + 2147483647 + + @@ -304,10 +310,16 @@ false 1 - true - false - false - + true + false + false + + + + 0 + 2147483647 + + diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml index 07f5823e4f..f4e068dc03 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml @@ -49,6 +49,10 @@ + + + + diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index dec2163d60..51cd214259 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -268,6 +268,12 @@ ${alfresco.groupId} alfresco-remote-api + + javax.servlet + javax.servlet-api + 3.1.0 + provided + io.takari.junit takari-cpsuite diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/FileplanComponentChildrenRelation.java b/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/FileplanComponentChildrenRelation.java index 9573442927..5c16af45ab 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/FileplanComponentChildrenRelation.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/FileplanComponentChildrenRelation.java @@ -31,6 +31,9 @@ import java.util.ArrayList; import java.util.List; import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.exceptions.ApiException; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; @@ -59,12 +62,14 @@ public class FileplanComponentChildrenRelation implements RelationshipResourceAc } @Override + @WebApiDescription(title = "Return a paged list of fileplan components for the container identified by parentFolderNodeId") public CollectionWithPagingInfo readAll(String parentFolderNodeId, Parameters parameters) { return nodes.listChildren(parentFolderNodeId, parameters); } @Override + @WebApiDescription(title="Create one (or more) nodes as children of container identified by parentFolderNodeId") public List create(String parentFolderNodeId, List nodeInfos, Parameters parameters) { List result = new ArrayList<>(nodeInfos.size()); @@ -78,8 +83,29 @@ public class FileplanComponentChildrenRelation implements RelationshipResourceAc } @Override + @WebApiDescription(title = "Upload file content and meta-data into the repository.") + @WebApiParam(name = "formData", title = "A single form data", description = "A single form data which holds FormFields.") public Node create(String parentFolderNodeId, FormData formData, Parameters parameters, WithResponse withResponse) { - return nodes.upload(parentFolderNodeId, formData, parameters); + try + { + return nodes.upload(parentFolderNodeId, formData, parameters); + } + catch (ApiException apiException) + { + /* + * The upload method encapsulates most exceptions that can occur on node creation in an ApiException. + * To allow the API framework to correctly map the exception to the API error code we throw the original exception. + */ + Throwable originalException = apiException.getCause(); + if (originalException != null && originalException instanceof RuntimeException) + { + throw (RuntimeException) originalException; + } + else + { + throw apiException; + } + } } } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/RecordsEntityResource.java b/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/RecordsEntityResource.java new file mode 100644 index 0000000000..215a43f49a --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/nodes/RecordsEntityResource.java @@ -0,0 +1,106 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.rm.rest.api.nodes; + +import java.io.InputStream; + +import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.framework.BinaryProperties; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; +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.rm.rest.api.RMNodes; +import org.alfresco.util.ParameterCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * An implementation of an Entity Resource for a record + * + * @author Ana Bozianu + * @since 2.6 + */ +@EntityResource(name="records", title = "Records") +public class RecordsEntityResource implements BinaryResourceAction.Update, + BinaryResourceAction.Read, + InitializingBean +{ + + private RMNodes nodes; + + public void setNodes(RMNodes nodes) + { + this.nodes = nodes; + } + + @Override + public void afterPropertiesSet() throws Exception + { + ParameterCheck.mandatory("nodes", this.nodes); + } + + /** + * Download content + * + * @param recordId the id of the record to get the content from + * @param parameters {@link Parameters} + * @return binary content resource + * @throws EntityNotFoundException + */ + @Override + @WebApiDescription(title = "Download content", description = "Download content") + @BinaryProperties({"content"}) + public BinaryResource readProperty(String recordId, Parameters parameters) throws EntityNotFoundException + { + return nodes.getContent(recordId, parameters, true); + } + + /** + * Upload new version of content + * + * This allow binary content update of an existing record. + * + * Note: alternatively, can upload via POST (multipart/form-data) with existing file name and form "overwrite=true". + * + * @param recordId the id of the record to set the content for + * @param contentInfo Basic information about the content stream + * @param stream an inputstream representing the new content of the node + * @param parameters {@link Parameters} + * @return information about the record that has been updated + */ + @Override + @WebApiDescription(title = "Upload content", description = "Upload content") + @BinaryProperties({"content"}) + public Node updateProperty(String recordId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters) + { + return nodes.updateContent(recordId, contentInfo, stream, parameters); + } +} diff --git a/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/ig-core-api.yaml b/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/ig-core-api.yaml index 447f030e15..ade891e07b 100644 --- a/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/ig-core-api.yaml +++ b/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/ig-core-api.yaml @@ -19,6 +19,8 @@ tags: description: Retrieve and manage fileplan components - name: ig-sites description: Retrieve and manage the RM site + - name: records + description: Perform record specific operations paths: '/fileplan-components/{fileplanComponentId}': get: @@ -47,6 +49,8 @@ paths: Invalid parameter: **fileplanComponentId** is not a valid format '401': description: If authentication fails + '403': + description: If current user does not have permission to read **fileplanComponentId** '404': description: If **fileplanComponentId** does not exist default: @@ -180,6 +184,8 @@ paths: $ref: '#/definitions/IGNodeAssociationPaging' '401': description: If authentication fails + '403': + description: If current user does not have permission to read **fileplanComponentId** '404': description: If **fileplanComponentId** does not exist default: @@ -194,9 +200,10 @@ paths: Create a fileplan component as a primary child of node **fileplanComponentId**. This API method supports file upload using multipart/form-data. + Electronic records are the only nodes that have content. Use the **filedata** field to represent the content to upload. - You can use a **name** field to give an alternative name for the new file. + You can use a **name** field to give an alternative name for the new electronic record. For multipart/form-data upload you can use the **renditions** field to create renditions (e.g. doclib) asynchronously upon upload. Note that currently only one rendition can be requested. Also, as requesting rendition is a background process, @@ -280,6 +287,14 @@ paths: } ``` + You can create an empty electronic record and use the record endpoint to create content: + ```JSON + { + "name":"My Electronic Record", + "nodeType":"cm:content" + } + ``` + You can create a fileplan component inside a container hierarchy: ```JSON { @@ -547,6 +562,123 @@ paths: description: Unexpected error schema: $ref: '#/definitions/Error' + '/records/{recordId}/content': + get: + x-alfresco-since: "5.2" + tags: + - records + summary: Get record content + description: | + + Gets the content of the record with identifier **recordId**. + operationId: getRecordContent + parameters: + - $ref: '#/parameters/recordIdParam' + - $ref: '#/parameters/attachmentParam' + - $ref: '#/parameters/ifModifiedSinceHeader' + responses: + '200': + description: Successful response + '304': + description: Content has not been modified since the date provided in the If-Modified-Since header + '400': + description: | + Invalid parameter: **nodeId** is not a valid format, or is not a file + '401': + description: Authentication failed + '404': + description: | + **nodeId** does not exist + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + put: + x-alfresco-since: "5.2" + tags: + - records + summary: Update record content + description: | + Updates the content of the record with identifier **recordId**. + + The request body for this endpoint can be any text or binary stream. + + The **majorVersion** and **comment** parameters can be used to control versioning behaviour. If the content is versionable, + a new minor version is created by default. + + Optionally a new **name** parameter can also be specified that must be unique within the parent folder. If specified and valid then this + will rename the node. If invalid then an error is returned and the content is not updated. + + **Note:** This API method accepts any content type, but for testing with this tool text based content can be provided. + This is because the OpenAPI Specification does not allow a wildcard to be provided or the ability for + tooling to accept an arbitrary file. + operationId: updateRecordContent + parameters: + - $ref: '#/parameters/recordIdParam' + - name: majorVersion + in: query + description: | + If **true**, create a major version. + Setting this parameter also enables versioning of this node, if it is not already versioned. + required: false + type: boolean + default: false + - name: comment + in: query + description: | + Add a version comment which will appear in version history. + Setting this parameter also enables versioning of this node, if it is not already versioned. + required: false + type: string + - name: name + in: query + description: | + Optional new name. This should include the file extension. + The name must not contain spaces or the following special characters: * " < > \ / ? : and |. + The character `.` must not be used at the end of the name. + required: false + type: string + pattern: "^(?!(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$))" + - $ref: '#/parameters/IGNodeEntryIncludeParam' + - $ref: '#/parameters/fieldsParam' + - in: body + name: contentBodyUpdate + description: The binary content + required: true + schema: + type: string + format: binary + produces: + - application/json + consumes: + - application/octet-stream + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/IGNodeEntry' + '400': + description: | + Invalid parameter: **recordId** is not a valid format, or is not a file + '401': + description: Authentication failed + '403': + description: Current user does not have permission to update **recordId** + '404': + description: | + **recordId** does not exist + '409': + description: Optional new name clashes with an existing node in the current parent folder + '413': + description: Content exceeds individual file size limit (configured for network/system) + '422': + description: Model integrity exception including a file name containing invalid characters + '507': + description: Content exceeds overall storage quota limit configured for the network/system + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' parameters: fileplanComponentIdWithAliasParam: name: fileplanComponentId @@ -609,13 +741,18 @@ parameters: items: type: string collectionFormat: csv - ## Core definition fileplanComponentIdParam: name: fileplanComponentId in: path description: The identifier of a fileplan compoment. required: true type: string + recordIdParam: + name: recordId + in: path + description: The identifier of a record. + required: true + type: string ## Core definition fieldsParam: name: fields @@ -670,6 +807,33 @@ parameters: items: type: string collectionFormat: csv + # Core definition + attachmentParam: + name: attachment + in: query + description: | + **true** enables a web browser to download the file as an attachment. + **false** means a web browser may preview the file in a new tab or window, but not + download the file. + + You can only set this parameter to **false** if the content type of the file is in the supported list; + for example, certain image files and PDF files. + + If the content type is not supported for preview, then a value of **false** is ignored, and + the attachment will be returned in the response. + required: false + default: true + type: boolean + # Core definition + ifModifiedSinceHeader: + name: If-Modified-Since + in: header + description: | + Only returns the content if it has been modified since the date provided. + Use the date format defined by HTTP. For example, `Wed, 09 Mar 2016 16:56:34 GMT`. + required: false + type: string + format: date-time definitions: IGNodeEntry: type: object