diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/core/RestAPIFactory.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/core/RestAPIFactory.java index 8fb21953f7..b07ec559b5 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/core/RestAPIFactory.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/core/RestAPIFactory.java @@ -39,6 +39,7 @@ import org.alfresco.rest.rm.community.requests.gscore.GSCoreAPI; import org.alfresco.rest.rm.community.requests.gscore.api.ActionsExecutionAPI; import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI; import org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI; +import org.alfresco.rest.rm.community.requests.gscore.api.HoldsAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RMUserAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RecordCategoryAPI; @@ -243,4 +244,14 @@ public class RestAPIFactory { return getGSCoreAPI(null).usingActionsExecutionsAPI(); } + + public HoldsAPI getHoldsAPI() + { + return getGSCoreAPI(null).usingHoldsAPI(); + } + + public HoldsAPI getHoldsAPI(UserModel userModel) + { + return getGSCoreAPI(userModel).usingHoldsAPI(); + } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java index 340306a746..435a4500be 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/fileplancomponents/FilePlanComponentFields.java @@ -61,7 +61,6 @@ public class FilePlanComponentFields public static final String PROPERTIES_RECORD_SEARCH_DISPOSITION_EVENTS = "rma:recordSearchDispositionEvents"; public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_BY = "rma:declassificationReviewCompletedBy"; public static final String PROPERTIES_DECLASSIFICATION_REVIEW_COMPLETED_AT = "rma:declassificationReviewCompletedAt"; - /** File plan properties */ public static final String PROPERTIES_COMPONENT_ID = "st:componentId"; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/Hold.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/Hold.java new file mode 100644 index 0000000000..07acb6efe5 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/Hold.java @@ -0,0 +1,83 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.alfresco.utility.model.TestModel; + +/** + * POJO for hold + * + * @author Damian Ujma + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Hold extends TestModel +{ + @JsonProperty(required = true) + private String id; + + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private String description; + + @JsonProperty(required = true) + private String reason; + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + Hold hold = (Hold) o; + return Objects.equals(id, hold.id) && Objects.equals(name, hold.name) + && Objects.equals(description, hold.description) && Objects.equals(reason, hold.reason); + } + + @Override + public int hashCode() + { + return Objects.hash(id, name, description, reason); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChild.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChild.java new file mode 100644 index 0000000000..e0fc841bda --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChild.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.alfresco.utility.model.TestModel; + +/** + * POJO for hold child + * + * @author Damian Ujma + */ +@Builder +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class HoldChild extends TestModel +{ + @JsonProperty(required = true) + private String id; +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildCollection.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildCollection.java new file mode 100644 index 0000000000..bdd3029d9f --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildCollection.java @@ -0,0 +1,38 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import org.alfresco.rest.core.RestModels; + +/** + * Handle collection of {@link HoldChildEntry} + * + * @author Damian Ujma + */ +public class HoldChildCollection extends RestModels +{ +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildEntry.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildEntry.java new file mode 100644 index 0000000000..e8a65dea78 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldChildEntry.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.alfresco.rest.core.RestModels; + +/** + * POJO for hold child entry + * + * @author Damian Ujma + */ +@Builder +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +public class HoldChildEntry extends RestModels +{ + @JsonProperty + private HoldChildEntry entry; +} \ No newline at end of file diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldCollection.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldCollection.java new file mode 100644 index 0000000000..687ba382db --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldCollection.java @@ -0,0 +1,38 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import org.alfresco.rest.core.RestModels; + +/** + * Handle collection of {@link HoldEntry} + * + * @author Damian Ujma + */ +public class HoldCollection extends RestModels +{ +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReason.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReason.java new file mode 100644 index 0000000000..fb964c553f --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReason.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.alfresco.utility.model.TestModel; + +/** + * POJO for hold deletion reason + * + * @author Damian Ujma + */ +@Builder +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +public class HoldDeletionReason extends TestModel +{ + @JsonProperty + private String reason; +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReasonEntry.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReasonEntry.java new file mode 100644 index 0000000000..e79b12a194 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldDeletionReasonEntry.java @@ -0,0 +1,52 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.alfresco.rest.core.RestModels; + +/** + * POJO for hold child entry + * + * @author Damian Ujma + */ +@Builder +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@AllArgsConstructor +public class HoldDeletionReasonEntry extends RestModels +{ + @JsonProperty + private HoldDeletionReason entry; +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldEntry.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldEntry.java index dbc2d562a0..48113e62d0 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldEntry.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/HoldEntry.java @@ -26,31 +26,27 @@ */ package org.alfresco.rest.rm.community.model.hold; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import org.alfresco.utility.model.TestModel; +import org.alfresco.rest.core.RestModels; /** * POJO for hold entry * - * @author Rodica Sutu - * @since 3.2 + * @author Damian Ujma */ @Builder @Data +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor -@JsonIgnoreProperties (ignoreUnknown = true) -public class HoldEntry extends TestModel +public class HoldEntry extends RestModels { - @JsonProperty (required = true) - private String name; - - @JsonProperty (required = true) - private String nodeRef; + @JsonProperty + private Hold entry; } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/v0/HoldEntry.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/v0/HoldEntry.java new file mode 100644 index 0000000000..02bda01970 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/model/hold/v0/HoldEntry.java @@ -0,0 +1,56 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold.v0; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.alfresco.utility.model.TestModel; + +/** + * POJO for hold entry + * + * @author Rodica Sutu + * @since 3.2 + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties (ignoreUnknown = true) +public class HoldEntry extends TestModel +{ + @JsonProperty (required = true) + private String name; + + @JsonProperty (required = true) + private String nodeRef; +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/GSCoreAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/GSCoreAPI.java index 04a8733b75..d1c6a6c2c7 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/GSCoreAPI.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/GSCoreAPI.java @@ -37,6 +37,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.ActionsExecutionAPI; import org.alfresco.rest.rm.community.requests.RMModelRequest; import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI; import org.alfresco.rest.rm.community.requests.gscore.api.FilesAPI; +import org.alfresco.rest.rm.community.requests.gscore.api.HoldsAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RMUserAPI; import org.alfresco.rest.rm.community.requests.gscore.api.RecordCategoryAPI; @@ -190,4 +191,6 @@ public class GSCoreAPI extends RMModelRequest { return new ActionsExecutionAPI(getRmRestWrapper()); } + + public HoldsAPI usingHoldsAPI() { return new HoldsAPI(getRmRestWrapper()); } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java index b47530d442..f7076f4ac2 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/FilePlanAPI.java @@ -38,6 +38,8 @@ import static org.springframework.http.HttpMethod.PUT; import org.alfresco.rest.core.RMRestWrapper; import org.alfresco.rest.rm.community.model.fileplan.FilePlan; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection; import org.alfresco.rest.rm.community.requests.RMModelRequest; @@ -213,4 +215,74 @@ public class FilePlanAPI extends RMModelRequest parameters)); } + /** + * Creates a hold. + * + * @param holdModel The hold model + * @param filePlanId The identifier of a file plan + * @param parameters The URL parameters to add + * @return The created {@link Hold} + * @throws RuntimeException for the following cases: + *
    + *
  • {@code filePlanId} is not a valid format or {@code filePlanId} is invalid
  • + *
  • authentication fails
  • + *
  • current user does not have permission to add children to {@code filePlanId}
  • + *
  • {@code filePlanIds} does not exist
  • + *
  • new name clashes with an existing node in the current parent container
  • + *
+ */ + public Hold createHold(Hold holdModel, String filePlanId, String parameters) + { + mandatoryString("filePlanId", filePlanId); + mandatoryObject("holdModel", holdModel); + + return getRmRestWrapper().processModel(Hold.class, requestWithBody( + POST, + toJson(holdModel), + "file-plans/{filePlanId}/holds", + filePlanId, + parameters + )); + } + + /** + * See {@link #createHold(Hold, String, String)} + */ + public Hold createHold(Hold holdModel, String filePlanId) + { + return createHold(holdModel, filePlanId, EMPTY); + } + + /** + * Gets the holds of a file plan. + * + * @param filePlanId The identifier of a file plan + * @param parameters The URL parameters to add + * @return The {@link HoldCollection} for the given {@code filePlanId} + * @throws RuntimeException for the following cases: + *
    + *
  • authentication fails
  • + *
  • current user does not have permission to read {@code filePlanId}
  • + *
  • {@code filePlanId} does not exist
  • + *
+ */ + public HoldCollection getHolds(String filePlanId, String parameters) + { + mandatoryString("filePlanId", filePlanId); + + return getRmRestWrapper().processModels(HoldCollection.class, simpleRequest( + GET, + "file-plans/{filePlanId}/holds?{parameters}", + filePlanId, + parameters + )); + } + + /** + * See {@link #getHolds(String, String)} + */ + public HoldCollection getHolds(String filePlanId) + { + return getHolds(filePlanId, EMPTY); + } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/HoldsAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/HoldsAPI.java new file mode 100644 index 0000000000..c06403290e --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/rm/community/requests/gscore/api/HoldsAPI.java @@ -0,0 +1,290 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.requests.gscore.api; + +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; +import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryString; +import static org.alfresco.rest.rm.community.util.PojoUtility.toJson; +import static org.apache.commons.lang3.StringUtils.EMPTY; +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 org.alfresco.rest.core.RMRestWrapper; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; +import org.alfresco.rest.rm.community.model.hold.HoldChildCollection; +import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason; +import org.alfresco.rest.rm.community.requests.RMModelRequest; + +/** + * Holds REST API Wrapper + * + * @author Damian Ujma + */ +public class HoldsAPI extends RMModelRequest +{ + + /** + * @param rmRestWrapper + */ + public HoldsAPI(RMRestWrapper rmRestWrapper) + { + super(rmRestWrapper); + } + + /** + * Gets a hold. + * + * @param holdId The identifier of a hold + * @param parameters The URL parameters to add + * @return The {@link Hold} for the given {@code holdId} + * @throws RuntimeException for the following cases: + *
    + *
  • {@code holdId} is not a valid format
  • + *
  • authentication fails
  • + *
  • current user does not have permission to read {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public Hold getHold(String holdId, String parameters) + { + mandatoryString("holdId", holdId); + + return getRmRestWrapper().processModel(Hold.class, simpleRequest( + GET, + "holds/{holdId}?{parameters}", + holdId, + parameters + )); + } + + /** + * See {@link #getHold(String, String)} + */ + public Hold getHold(String holdId) + { + mandatoryString("holdId", holdId); + + return getHold(holdId, EMPTY); + } + + /** + * Updates a hold. + * + * @param holdModel The hold model which holds the information + * @param holdId The identifier of the hold + * @param parameters The URL parameters to add + * @throws RuntimeException for the following cases: + *
    + *
  • the update request is invalid or {@code holdId} is not a valid format or {@code holdModel} is invalid
  • + *
  • authentication fails
  • + *
  • current user does not have permission to update {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public Hold updateHold(Hold holdModel, String holdId, String parameters) + { + mandatoryObject("holdModel", holdModel); + mandatoryString("holdId", holdId); + + return getRmRestWrapper().processModel(Hold.class, requestWithBody( + PUT, + toJson(holdModel), + "holds/{holdId}?{parameters}", + holdId, + parameters + )); + } + + /** + * See {@link #updateHold(Hold, String, String)} + */ + public Hold updateHold(Hold holdModel, String holdId) + { + mandatoryObject("holdModel", holdModel); + mandatoryString("holdId", holdId); + + return updateHold(holdModel, holdId, EMPTY); + } + + /** + * Deletes a hold. + * + * @param holdId The identifier of a hold + * @throws RuntimeException for the following cases: + *
    + *
  • {@code holdId} is not a valid format
  • + *
  • authentication fails
  • + *
  • current user does not have permission to delete {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public void deleteHold(String holdId) + { + mandatoryString("holdId", holdId); + + getRmRestWrapper().processEmptyModel(simpleRequest( + DELETE, + "holds/{holdId}", + holdId + )); + } + + /** + * Deletes a hold and stores a reason for deletion in the audit log. + * + * @param reason The reason for hold deletion + * @param holdId The identifier of a hold + * @throws RuntimeException for the following cases: + *
    + *
  • {@code holdId} is not a valid format or {@code reason} is invalid
  • + *
  • authentication fails
  • + *
  • current user does not have permission to delete {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public HoldDeletionReason deleteHoldWithReason(HoldDeletionReason reason, String holdId) + { + mandatoryObject("reason", reason); + mandatoryString("holdId", holdId); + + return getRmRestWrapper().processModel(HoldDeletionReason.class, requestWithBody( + POST, + toJson(reason), + "holds/{holdId}/delete", + holdId + )); + } + + /** + * Adds the relationship between a child and a parent hold. + * + * @param holdChild The hold child model + * @param holdId The identifier of a hold + * @param parameters The URL parameters to add + * @return The created {@link Hold} + * @throws RuntimeException for the following cases: + *
    + *
  • {@code holdId} is not a valid format or {@code holdId} is invalid
  • + *
  • authentication fails
  • + *
  • current user does not have permission to add children to {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public HoldChild addChildToHold(HoldChild holdChild, String holdId, String parameters) + { + mandatoryObject("holdId", holdId); + + return getRmRestWrapper().processModel(HoldChild.class, requestWithBody( + POST, + toJson(holdChild), + "holds/{holdId}/children", + holdId, + parameters)); + } + + /** + * See {@link #addChildToHold(HoldChild, String, String)} + */ + public HoldChild addChildToHold(HoldChild holdChild, String holdId) + { + return addChildToHold(holdChild, holdId, EMPTY); + } + + /** + * Gets the children of a hold. + * + * @param holdId The identifier of a hold + * @param parameters The URL parameters to add + * @return The {@link HoldChildCollection} for the given {@code holdId} + * @throws RuntimeException for the following cases: + *
    + *
  • authentication fails
  • + *
  • current user does not have permission to read {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public HoldChildCollection getChildren(String holdId, String parameters) + { + mandatoryString("holdId", holdId); + + return getRmRestWrapper().processModels(HoldChildCollection.class, simpleRequest( + GET, + "holds/{holdId}/children", + holdId, + parameters + )); + } + + /** + * See {@link #getChildren(String, String)} + */ + public HoldChildCollection getChildren(String holdId) + { + return getChildren(holdId, EMPTY); + } + + /** + * Deletes the relationship between a child and a parent hold. + * + * @param holdChildId The identifier of hold child + * @param holdId The identifier of a hold + * @param parameters The URL parameters to add + * @throws RuntimeException for the following cases: + *
    + *
  • {@code holdId} or {@code holdChildId} is invalid
  • + *
  • authentication fails
  • + *
  • current user does not have permission to delete children from {@code holdId}
  • + *
  • {@code holdId} does not exist
  • + *
+ */ + public void deleteHoldChild(String holdId, String holdChildId, String parameters) + { + mandatoryString("holdId", holdId); + mandatoryString("holdChildId", holdChildId); + + getRmRestWrapper().processEmptyModel(simpleRequest( + DELETE, + "holds/{holdId}/children/{holdChildId}", + holdId, + holdChildId, + parameters + )); + } + + /** + * See {@link #deleteHoldChild(String, String, String)} + */ + public void deleteHoldChild(String holdId, String holdChildId) + { + deleteHoldChild(holdId, holdChildId, EMPTY); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/HoldsAPI.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/HoldsAPI.java index 3ff5a0405d..c1798df0c3 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/HoldsAPI.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/main/java/org/alfresco/rest/v0/HoldsAPI.java @@ -36,7 +36,7 @@ import java.util.stream.Collectors; import org.alfresco.rest.core.v0.APIUtils; import org.alfresco.rest.core.v0.BaseAPI; -import org.alfresco.rest.rm.community.model.hold.HoldEntry; +import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry; import org.alfresco.rest.rm.community.util.PojoUtility; import org.alfresco.utility.model.UserModel; import org.apache.http.HttpResponse; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditAddToHoldTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditAddToHoldTests.java index 0cd0acaf47..ac9edd1c3e 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditAddToHoldTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditAddToHoldTests.java @@ -31,18 +31,18 @@ import static java.util.Arrays.asList; import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.ADD_TO_HOLD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; import static org.alfresco.rest.rm.community.utils.RMSiteUtil.FILE_PLAN_PATH; import static org.alfresco.utility.Utility.buildPath; import static org.alfresco.utility.Utility.removeLastSlash; import static org.alfresco.utility.data.RandomData.getRandomName; import static org.alfresco.utility.report.log.Step.STEP; -import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.google.common.collect.ImmutableMap; @@ -50,12 +50,13 @@ import com.google.common.collect.ImmutableMap; import org.alfresco.dataprep.CMISUtil; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.model.audit.AuditEntry; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; 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.rm.community.model.user.UserPermissions; import org.alfresco.rest.rm.community.model.user.UserRoles; -import org.alfresco.rest.v0.HoldsAPI; import org.alfresco.rest.v0.service.RMAuditService; import org.alfresco.rest.v0.service.RoleService; import org.alfresco.test.AlfrescoTest; @@ -85,8 +86,6 @@ public class AuditAddToHoldTests extends BaseRMRestTest @Autowired private RMAuditService rmAuditService; @Autowired - private HoldsAPI holdsAPI; - @Autowired private RoleService roleService; private UserModel rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode; @@ -94,17 +93,22 @@ public class AuditAddToHoldTests extends BaseRMRestTest private RecordCategory recordCategory; private RecordCategoryChild recordFolder; private List auditEntries; - private final List holdsList = asList(HOLD1, HOLD2); private List holdsListRef = new ArrayList<>(); private String hold1NodeRef; + private String hold2NodeRef; @BeforeClass (alwaysRun = true) public void preconditionForAuditAddToHoldTests() { STEP("Create 2 holds."); - hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), - getAdminUser().getPassword(), HOLD1, HOLD_REASON, HOLD_DESCRIPTION); - String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION); + hold1NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); + hold2NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); holdsListRef = asList(hold1NodeRef, hold2NodeRef); STEP("Create a new record category with a record folder."); @@ -169,7 +173,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Add node to hold."); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), nodeId, HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(nodeId).build(), hold1NodeRef); + STEP("Check the audit log contains the entry for the add to hold event."); rmAuditService.checkAuditLogForEvent(getAdminUser(), ADD_TO_HOLD, rmAdmin, nodeName, nodePath, @@ -191,9 +196,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Try to add the record to a hold by an user with no rights."); - holdsAPI.addItemsToHolds(rmManagerNoReadOnHold.getUsername(), rmManagerNoReadOnHold.getPassword(), - SC_INTERNAL_SERVER_ERROR, Collections.singletonList(recordToBeAdded.getId()), - Collections.singletonList(hold1NodeRef)); + getRestAPIFactory().getHoldsAPI(rmManagerNoReadOnHold).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef); + assertStatusCode(FORBIDDEN); STEP("Check the audit log doesn't contain the entry for the unsuccessful add to hold."); assertTrue("The list of events should not contain Add to Hold entry ", @@ -215,7 +219,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Add record folder to hold."); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(notEmptyRecFolder.getId()).build(), hold1NodeRef); auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), ADD_TO_HOLD); @@ -239,8 +243,9 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Add record to multiple holds."); - holdsAPI.addItemsToHolds(rmAdmin.getUsername(), rmAdmin.getPassword(), - Collections.singletonList(recordToBeAdded.getId()), holdsList); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold2NodeRef); + auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), ADD_TO_HOLD); @@ -268,7 +273,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Add file to hold."); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), contentToBeAdded.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(contentToBeAdded.getNodeRefWithoutVersion()).build(), hold1NodeRef); STEP("Check that an user with no Read permissions can't see the entry for the add to hold event."); assertTrue("The list of events should not contain Add to Hold entry ", @@ -289,7 +294,7 @@ public class AuditAddToHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Add file to hold."); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), contentToBeAdded.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(contentToBeAdded.getNodeRefWithoutVersion()).build(), hold1NodeRef); auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(rmManagerNoReadOnHold, ADD_TO_HOLD); @@ -304,7 +309,8 @@ public class AuditAddToHoldTests extends BaseRMRestTest @AfterClass (alwaysRun = true) public void cleanUpAuditAddToHoldTests() { - holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef)); + holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdRef)); + dataSite.usingAdmin().deleteSite(privateSite); asList(rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode).forEach(user -> getDataUser().usingAdmin().deleteUser(user)); deleteRecordCategory(recordCategory.getId()); diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditCreateHoldTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditCreateHoldTests.java index 8f0e1e6415..d5054d640e 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditCreateHoldTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditCreateHoldTests.java @@ -31,9 +31,10 @@ import static java.util.Arrays.asList; import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.CREATE_HOLD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; import static org.alfresco.utility.report.log.Step.STEP; -import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.CONFLICT; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; @@ -44,8 +45,8 @@ import com.google.common.collect.ImmutableMap; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.model.audit.AuditEntry; +import org.alfresco.rest.rm.community.model.hold.Hold; import org.alfresco.rest.rm.community.model.user.UserRoles; -import org.alfresco.rest.v0.HoldsAPI; import org.alfresco.rest.v0.service.RMAuditService; import org.alfresco.rest.v0.service.RoleService; import org.alfresco.test.AlfrescoTest; @@ -73,8 +74,6 @@ public class AuditCreateHoldTests extends BaseRMRestTest @Autowired private RMAuditService rmAuditService; @Autowired - private HoldsAPI holdsAPI; - @Autowired private RoleService roleService; private UserModel rmAdmin, rmManager; @@ -102,8 +101,10 @@ public class AuditCreateHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Create a new hold."); - String hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD1, - HOLD_REASON, HOLD_DESCRIPTION); + String hold1NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); holdsListRef.add(hold1NodeRef); STEP("Check the audit log contains the entry for the created hold with the hold details."); rmAuditService.checkAuditLogForEvent(getAdminUser(), CREATE_HOLD, rmAdmin, HOLD1, @@ -120,13 +121,18 @@ public class AuditCreateHoldTests extends BaseRMRestTest public void createHoldEventIsNotAuditedForExistingHold() { STEP("Create a new hold."); - String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION); + String hold2NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); holdsListRef.add(hold2NodeRef); rmAuditService.clearAuditLog(); STEP("Try to create again the same hold and expect action to fail."); - holdsAPI.createHold(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION, - SC_INTERNAL_SERVER_ERROR); + getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS); + assertStatusCode(CONFLICT); STEP("Check the audit log doesn't contain the entry for the second create hold event."); assertTrue("The list of events should not contain Create Hold entry ", @@ -145,13 +151,17 @@ public class AuditCreateHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Create a new hold."); - holdsAPI.createHold(rmAdmin.getUsername(), rmAdmin.getPassword(), holdName, HOLD_REASON, HOLD_DESCRIPTION); + String nodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(holdName).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS).getId(); STEP("Get the list of audit entries for the create hold event."); List auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), CREATE_HOLD); STEP("Delete the created hold."); - holdsAPI.deleteHold(rmAdmin.getUsername(), rmAdmin.getPassword(), holdName); + getRestAPIFactory() + .getHoldsAPI(rmAdmin) + .deleteHold(nodeRef); STEP("Get again the list of audit entries for the create hold event."); List auditEntriesAfterDelete = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), CREATE_HOLD); @@ -171,8 +181,10 @@ public class AuditCreateHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Create a new hold."); - String hold3NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD3, - HOLD_REASON, HOLD_DESCRIPTION); + String hold3NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS).getId(); + holdsListRef.add(hold3NodeRef); STEP("Check that an user with no Read permissions over the hold can't see the entry for the create hold event"); @@ -183,7 +195,7 @@ public class AuditCreateHoldTests extends BaseRMRestTest @AfterClass (alwaysRun = true) public void cleanUpAuditCreateHoldTests() { - holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef)); + holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef)); asList(rmAdmin, rmManager).forEach(user -> getDataUser().usingAdmin().deleteUser(user)); } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditDeleteHoldTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditDeleteHoldTests.java index 72a5cf89b3..a77f55290e 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditDeleteHoldTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditDeleteHoldTests.java @@ -31,18 +31,20 @@ import static java.util.Arrays.asList; import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.DELETE_HOLD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; import static org.alfresco.utility.report.log.Step.STEP; -import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.testng.AssertJUnit.assertTrue; -import java.util.Collections; +import java.util.List; import com.google.common.collect.ImmutableMap; import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason; import org.alfresco.rest.rm.community.model.user.UserRoles; -import org.alfresco.rest.v0.HoldsAPI; import org.alfresco.rest.v0.service.RMAuditService; import org.alfresco.rest.v0.service.RoleService; import org.alfresco.test.AlfrescoTest; @@ -62,14 +64,13 @@ import org.testng.annotations.Test; public class AuditDeleteHoldTests extends BaseRMRestTest { private final String PREFIX = generateTestPrefix(AuditDeleteHoldTests.class); - private final String HOLD = PREFIX + "holdToBeDeleted"; - private final String HOLD2 = PREFIX + "deleteHold"; + private final String hold = PREFIX + "holdToBeDeleted"; + private final String hold2 = PREFIX + "deleteHold"; + private final String hold3 = PREFIX + "deleteHoldWithReason"; @Autowired private RMAuditService rmAuditService; @Autowired - private HoldsAPI holdsAPI; - @Autowired private RoleService roleService; private UserModel rmAdmin, rmManager; @@ -79,8 +80,10 @@ public class AuditDeleteHoldTests extends BaseRMRestTest public void preconditionForAuditDeleteHoldTests() { STEP("Create a new hold."); - holdNodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD, - HOLD_REASON, HOLD_DESCRIPTION); + holdNodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(hold).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); STEP("Create 2 users with different permissions for the created hold."); rmAdmin = roleService.createUserWithRMRole(UserRoles.ROLE_RM_ADMIN.roleId); @@ -99,17 +102,51 @@ public class AuditDeleteHoldTests extends BaseRMRestTest public void deleteHoldEventIsAudited() { STEP("Create a new hold."); - String holdRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD2, - HOLD_REASON, HOLD_DESCRIPTION); + String holdRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(hold2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); rmAuditService.clearAuditLog(); STEP("Delete the created hold."); - holdsAPI.deleteHold(rmAdmin, holdRef); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef); STEP("Check the audit log contains the entry for the deleted hold with the hold details."); - rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, HOLD2, - Collections.singletonList(ImmutableMap.of("new", "", "previous", HOLD2, "name", "Hold Name"))); + rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, hold2, + List.of(ImmutableMap.of("new", "", "previous", hold2, "name", "Hold Name"), + ImmutableMap.of("new", "", "previous", "", "name", "Hold deletion reason"))); + } + + /** + * Given a hold is deleted with a reason + * When I view the audit log + * Then an entry has been created in the audit log which contains the following: + * name of the hold + * hold deletion reason + * user who deleted the hold + * date the delete occurred + */ + @Test + public void deleteHoldWithReasonEventIsAudited() + { + STEP("Create a new hold."); + String holdRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(hold3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); + + String deletionReason = "Test reason"; + + rmAuditService.clearAuditLog(); + + STEP("Delete the created hold with a reason."); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldWithReason(HoldDeletionReason.builder().reason(deletionReason).build(), holdRef); + + STEP("Check the audit log contains the entry for the deleted hold with the hold details."); + rmAuditService.checkAuditLogForEvent(getAdminUser(), DELETE_HOLD, rmAdmin, hold3, + List.of(ImmutableMap.of("new", "", "previous", hold3, "name", "Hold Name"), + ImmutableMap.of("new", "", "previous", deletionReason, "name", "Hold deletion reason"))); } /** @@ -123,7 +160,8 @@ public class AuditDeleteHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Try to delete a hold by an user with no Read permissions over the hold."); - holdsAPI.deleteHold(rmManager.getUsername(), rmManager.getPassword(), holdNodeRef, SC_INTERNAL_SERVER_ERROR); + getRestAPIFactory().getHoldsAPI(rmManager).deleteHold(holdNodeRef); + assertStatusCode(FORBIDDEN); STEP("Check the audit log doesn't contain the entry for the unsuccessful delete hold."); assertTrue("The list of events should not contain Delete Hold entry ", @@ -133,7 +171,7 @@ public class AuditDeleteHoldTests extends BaseRMRestTest @AfterClass (alwaysRun = true) public void cleanUpAuditDeleteHoldTests() { - holdsAPI.deleteHold(getAdminUser(), holdNodeRef); + getRestAPIFactory().getHoldsAPI(rmManager).deleteHold(holdNodeRef); asList(rmAdmin, rmManager).forEach(user -> getDataUser().usingAdmin().deleteUser(user)); } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditHoldsTest.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditHoldsTest.java index d045bcc954..67b6c8d38b 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditHoldsTest.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditHoldsTest.java @@ -25,10 +25,14 @@ * #L% */ package org.alfresco.rest.rm.community.audit; + import static java.util.Arrays.asList; -import static org.alfresco.rest.rm.community.base.TestData.*; + +import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; +import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.ADD_TO_HOLD; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.REMOVE_FROM_HOLD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; import static org.alfresco.utility.data.RandomData.getRandomName; import static org.alfresco.utility.report.log.Step.STEP; @@ -37,20 +41,22 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.IsNot.not; import static org.springframework.http.HttpStatus.CREATED; -import static org.testng.AssertJUnit.*; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertTrue; -import java.util.Collections; import java.util.List; + import org.alfresco.dataprep.CMISUtil; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.model.audit.AuditEntry; import org.alfresco.rest.rm.community.model.audit.AuditEvents; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; 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.rm.community.model.recordfolder.RecordFolder; import org.alfresco.rest.rm.community.model.user.UserRoles; -import org.alfresco.rest.v0.HoldsAPI; import org.alfresco.rest.v0.service.RMAuditService; import org.alfresco.rest.v0.service.RoleService; import org.alfresco.utility.model.FileModel; @@ -69,8 +75,6 @@ public class AuditHoldsTest extends BaseRMRestTest { @Autowired private RMAuditService rmAuditService; @Autowired - private HoldsAPI holdsAPI; - @Autowired private RoleService roleService; private UserModel rmAdmin; private RecordCategory recordCategory; @@ -85,8 +89,11 @@ public class AuditHoldsTest extends BaseRMRestTest { rmAdmin = roleService.createUserWithRMRole(UserRoles.ROLE_RM_ADMIN.roleId); STEP("Create a hold"); - hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(rmAdmin.getUsername(), rmAdmin.getPassword(), HOLD1, HOLD_REASON, - HOLD_DESCRIPTION); + + hold1NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); STEP("Create a collaboration site with a test file."); publicSite = dataSite.usingAdmin().createPublicRandomSite(); @@ -101,9 +108,11 @@ public class AuditHoldsTest extends BaseRMRestTest { STEP("Add some items to the hold, then remove them from the hold"); final List itemsList = asList(testFile.getNodeRefWithoutVersion(), recordToBeAdded.getId(), recordFolder2.getId()); - final List holdsList = Collections.singletonList(HOLD1); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), recordToBeAdded.getId(), HOLD1); - holdsAPI.removeItemsFromHolds(rmAdmin.getUsername(), rmAdmin.getPassword(), itemsList, holdsList); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(recordToBeAdded.getId()).build(), hold1NodeRef); + for(String childId : itemsList) + { + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, childId); + } STEP("Delete the record folder that was held"); getRestAPIFactory().getRecordFolderAPI().deleteRecordFolder(recordFolder2.getId()); diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditRemoveFromHoldTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditRemoveFromHoldTests.java index 85383069ac..831cdae9d9 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditRemoveFromHoldTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/audit/AuditRemoveFromHoldTests.java @@ -31,18 +31,18 @@ import static java.util.Arrays.asList; import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; import static org.alfresco.rest.rm.community.model.audit.AuditEvents.REMOVE_FROM_HOLD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; import static org.alfresco.rest.rm.community.utils.RMSiteUtil.FILE_PLAN_PATH; import static org.alfresco.utility.Utility.buildPath; import static org.alfresco.utility.Utility.removeLastSlash; import static org.alfresco.utility.data.RandomData.getRandomName; import static org.alfresco.utility.report.log.Step.STEP; -import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import com.google.common.collect.ImmutableMap; @@ -50,12 +50,13 @@ import com.google.common.collect.ImmutableMap; import org.alfresco.dataprep.CMISUtil; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.model.audit.AuditEntry; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; 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.rm.community.model.user.UserPermissions; import org.alfresco.rest.rm.community.model.user.UserRoles; -import org.alfresco.rest.v0.HoldsAPI; import org.alfresco.rest.v0.service.RMAuditService; import org.alfresco.rest.v0.service.RoleService; import org.alfresco.test.AlfrescoTest; @@ -86,8 +87,6 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest @Autowired private RMAuditService rmAuditService; @Autowired - private HoldsAPI holdsAPI; - @Autowired private RoleService roleService; private UserModel rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode; @@ -96,10 +95,11 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest private RecordCategoryChild recordFolder, heldRecordFolder; private Record heldRecord; private List auditEntries; - private final List holdsList = asList(HOLD1, HOLD2, HOLD3); private List holdsListRef = new ArrayList<>(); private FileModel heldContent; private String hold1NodeRef; + private String hold2NodeRef; + private String hold3NodeRef; @BeforeClass (alwaysRun = true) public void preconditionForAuditRemoveFromHoldTests() @@ -111,10 +111,18 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest privateSite = dataSite.usingUser(rmAdmin).createPrivateRandomSite(); STEP("Create new holds."); - hold1NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), - HOLD1, HOLD_REASON, HOLD_DESCRIPTION); - String hold2NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD2, HOLD_REASON, HOLD_DESCRIPTION); - String hold3NodeRef = holdsAPI.createHoldAndGetNodeRef(getAdminUser().getUsername(), getAdminUser().getPassword(), HOLD3, HOLD_REASON, HOLD_DESCRIPTION); + hold1NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD1).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); + hold2NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD2).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); + hold3NodeRef = getRestAPIFactory() + .getFilePlansAPI(rmAdmin) + .createHold(Hold.builder().name(HOLD3).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), FILE_PLAN_ALIAS) + .getId(); holdsListRef = asList(hold1NodeRef, hold2NodeRef, hold3NodeRef); STEP("Create a new record category with a record folder."); @@ -127,9 +135,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest heldRecordFolder = createRecordFolder(recordCategory.getId(), PREFIX + "heldRecFolder"); heldRecord = createElectronicRecord(recordFolder.getId(), PREFIX + "record"); - holdsAPI.addItemsToHolds(getAdminUser().getUsername(), getAdminUser().getPassword(), - asList(heldContent.getNodeRefWithoutVersion(), heldRecordFolder.getId(), heldRecord.getId()), - holdsList); + holdsListRef.forEach(holdRef -> + { + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldContent.getNodeRefWithoutVersion()).build(), holdRef); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldRecordFolder.getId()).build(), holdRef); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldRecord.getId()).build(), holdRef); + }); STEP("Create users without rights to remove content from a hold."); rmManagerNoReadOnHold = roleService.createUserWithSiteRoleRMRoleAndPermission(privateSite, @@ -179,7 +190,7 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Remove node from hold."); - holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), nodeId, HOLD3); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold3NodeRef, nodeId); STEP("Check the audit log contains the entry for the remove from hold event."); rmAuditService.checkAuditLogForEvent(getAdminUser(), REMOVE_FROM_HOLD, rmAdmin, nodeName, nodePath, @@ -198,9 +209,8 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Try to remove the record from a hold by an user with no rights."); - holdsAPI.removeItemsFromHolds(rmManagerNoReadOnHold.getUsername(), rmManagerNoReadOnHold.getPassword(), - SC_INTERNAL_SERVER_ERROR, Collections.singletonList(heldRecord.getId()), - Collections.singletonList(hold1NodeRef)); + getRestAPIFactory().getHoldsAPI(rmManagerNoReadOnHold).deleteHoldChild(hold1NodeRef, heldRecord.getId()); + assertStatusCode(FORBIDDEN); STEP("Check the audit log doesn't contain the entry for the unsuccessful remove from hold."); assertTrue("The list of events should not contain remove from hold entry ", @@ -220,12 +230,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest Record record = createElectronicRecord(notEmptyRecFolder.getId(), PREFIX + "record"); STEP("Add the record folder to a hold."); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(notEmptyRecFolder.getId()).build(), hold1NodeRef); rmAuditService.clearAuditLog(); STEP("Remove record folder from hold."); - holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), notEmptyRecFolder.getId(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, notEmptyRecFolder.getId()); STEP("Get the list of audit entries for the remove from hold event."); auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), REMOVE_FROM_HOLD); @@ -247,8 +257,8 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest rmAuditService.clearAuditLog(); STEP("Remove record folder from multiple holds."); - holdsAPI.removeItemsFromHolds(rmAdmin.getUsername(), rmAdmin.getPassword(), - Collections.singletonList(heldRecordFolder.getId()), asList(HOLD1, HOLD2)); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldRecordFolder.getId()); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold2NodeRef, heldRecordFolder.getId()); STEP("Get the list of audit entries for the remove from hold event."); auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(getAdminUser(), REMOVE_FROM_HOLD); @@ -275,12 +285,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest STEP("Add content to a hold."); FileModel heldFile = dataContent.usingAdmin().usingSite(privateSite) .createContent(CMISUtil.DocumentType.TEXT_PLAIN); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldFile.getNodeRefWithoutVersion()).build(), hold1NodeRef); rmAuditService.clearAuditLog(); STEP("Remove held content from the hold."); - holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldFile.getNodeRefWithoutVersion()); STEP("Check that an user with no Read permissions can't see the entry for the remove from hold event."); assertTrue("The list of events should not contain Remove from Hold entry ", @@ -298,12 +308,12 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest STEP("Add content to a hold."); FileModel heldFile = dataContent.usingAdmin().usingSite(privateSite) .createContent(CMISUtil.DocumentType.TEXT_PLAIN); - holdsAPI.addItemToHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).addChildToHold(HoldChild.builder().id(heldFile.getNodeRefWithoutVersion()).build(), hold1NodeRef); rmAuditService.clearAuditLog(); STEP("Remove held content from the hold."); - holdsAPI.removeItemFromHold(rmAdmin.getUsername(), rmAdmin.getPassword(), heldFile.getNodeRefWithoutVersion(), HOLD1); + getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHoldChild(hold1NodeRef, heldFile.getNodeRefWithoutVersion()); auditEntries = rmAuditService.getAuditEntriesFilteredByEvent(rmManagerNoReadOnHold, REMOVE_FROM_HOLD); @@ -318,7 +328,7 @@ public class AuditRemoveFromHoldTests extends BaseRMRestTest @AfterClass (alwaysRun = true) public void cleanUpAuditRemoveFromHoldTests() { - holdsListRef.forEach(holdRef -> holdsAPI.deleteHold(getAdminUser(), holdRef)); + holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(rmAdmin).deleteHold(holdRef)); dataSite.usingAdmin().deleteSite(privateSite); asList(rmAdmin, rmManagerNoReadOnHold, rmManagerNoReadOnNode).forEach(user -> getDataUser().usingAdmin().deleteUser(user)); deleteRecordCategory(recordCategory.getId()); diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java index 3ed53671dd..4ad252ef94 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/fileplans/FilePlanTests.java @@ -60,12 +60,15 @@ import static org.testng.Assert.fail; import static org.testng.AssertJUnit.assertEquals; import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; import org.alfresco.rest.rm.community.base.BaseRMRestTest; import org.alfresco.rest.rm.community.base.DataProviderClass; import org.alfresco.rest.rm.community.model.fileplan.FilePlan; import org.alfresco.rest.rm.community.model.fileplan.FilePlanProperties; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryProperties; @@ -514,5 +517,97 @@ public class FilePlanTests extends BaseRMRestTest ); } + /** + *
+     * Given that a file plan exists
+     * When I ask the API to create a hold
+     * Then it is created
+     * 
+ */ + @Test + public void createHolds() + { + String holdName = "Hold" + getRandomAlphanumeric(); + String holdDescription = "Description" + getRandomAlphanumeric(); + String holdReason = "Reason" + getRandomAlphanumeric(); + // Create the hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + + // Verify the status code + assertStatusCode(CREATED); + + assertEquals(createdHold.getName(), holdName); + assertEquals(createdHold.getDescription(), holdDescription); + assertEquals(createdHold.getReason(), holdReason); + assertNotNull(createdHold.getId()); + } + + + @Test + public void listHolds() + { + // Delete all holds + getRestAPIFactory().getFilePlansAPI().getHolds(FILE_PLAN_ALIAS).getEntries().forEach(holdEntry -> + getRestAPIFactory().getHoldsAPI().deleteHold(holdEntry.getEntry().getId())); + + // Add holds + List filePlanHolds = new ArrayList<>(); + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) + { + String holdName = "Hold name " + getRandomAlphanumeric(); + String holdDescription = "Hold Description " + getRandomAlphanumeric(); + String holdReason = "Reason " + getRandomAlphanumeric(); + // Create a hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + assertNotNull(createdHold.getId()); + filePlanHolds.add(createdHold); + } + + // Get holds of a file plan + HoldCollection holdCollection = getRestAPIFactory().getFilePlansAPI() + .getHolds(FILE_PLAN_ALIAS); + + // Check status code + assertStatusCode(OK); + + // Check holds against created list + holdCollection.getEntries().forEach(c -> + { + Hold hold = c.getEntry(); + String holdId = hold.getId(); + assertNotNull(holdId); + logger.info("Checking hold " + holdId); + + try + { + // Find this hold in created holds list + Hold createdHold = filePlanHolds.stream() + .filter(child -> child.getId().equals(holdId)) + .findFirst() + .orElseThrow(); + + assertEquals(createdHold.getName(), hold.getName()); + assertEquals(createdHold.getDescription(), hold.getDescription()); + assertEquals(createdHold.getReason(), hold.getReason()); + } + catch (NoSuchElementException e) + { + fail("No child element for " + hold); + } + } + ); + } } diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsTests.java index 28f9cada61..aaa52f71c0 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsTests.java @@ -60,7 +60,7 @@ import org.alfresco.dataprep.CMISUtil; import org.alfresco.dataprep.ContentActions; import org.alfresco.rest.model.RestNodeModel; import org.alfresco.rest.rm.community.base.BaseRMRestTest; -import org.alfresco.rest.rm.community.model.hold.HoldEntry; +import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry; 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; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsV1Tests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsV1Tests.java new file mode 100644 index 0000000000..3c240e9704 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/AddToHoldsV1Tests.java @@ -0,0 +1,386 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; +import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.TRANSFERS_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.UNFILED_RECORDS_CONTAINER_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.FROZEN_ASPECT; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE; +import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING; +import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER; +import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; +import static org.alfresco.rest.rm.community.utils.CoreUtil.toContentModel; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile; +import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.apache.commons.httpclient.HttpStatus.SC_BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.assertFalse; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.dataprep.CMISUtil; +import org.alfresco.dataprep.ContentActions; +import org.alfresco.rest.model.RestNodeAssociationModelCollection; +import org.alfresco.rest.model.RestNodeModel; +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; +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.rm.community.model.user.UserRoles; +import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI; +import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI; +import org.alfresco.rest.v0.service.RoleService; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.UserModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * V1 API tests for adding content/record folder/records to holds + * + * @author Damian Ujma + */ +public class AddToHoldsV1Tests extends BaseRMRestTest +{ + private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " + + "permissions to perform this operation."; + private static final String INVALID_TYPE_ERROR_MESSAGE = "Only records, record folders or content can be added to a hold."; + private static final String LOCKED_FILE_ERROR_MESSAGE = "Locked content can't be added to a hold."; + + private static final String HOLD = "HOLD" + generateTestPrefix(AddToHoldsV1Tests.class); + private String holdNodeRef; + private SiteModel testSite; + private FileModel documentHeld; + private FileModel contentToAddToHold; + private FileModel contentAddToHoldNoPermission; + private Hold hold; + + private UserModel userAddHoldPermission; + private final List users = new ArrayList<>(); + private final List nodesToBeClean = new ArrayList<>(); + + @Autowired + private RoleService roleService; + @Autowired + private ContentActions contentActions; + + @BeforeClass(alwaysRun = true) + public void preconditionForAddContentToHold() + { + STEP("Create a hold."); + hold = createHold(FILE_PLAN_ALIAS, + Hold.builder().name(HOLD).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), getAdminUser()); + holdNodeRef = hold.getId(); + STEP("Create test files."); + testSite = dataSite.usingAdmin().createPublicRandomSite(); + documentHeld = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + contentToAddToHold = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + contentAddToHoldNoPermission = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + + STEP("Add the content to the hold."); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(documentHeld.getNodeRefWithoutVersion()).build(), hold.getId()); + + STEP("Create users"); + userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, + UserRole.SiteCollaborator, holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING); + users.add(userAddHoldPermission); + } + + /** + * Given a hold that contains at least one active content + * When I use the existing REST API to retrieve the contents of the hold + * Then I should see all the active content on hold + */ + @Test + public void retrieveTheContentOfTheHoldUsingV1API() + { + STEP("Retrieve the list of children from the hold and collect the entries that have the name of the active " + + "content held"); + List documentNames = restClient.authenticateUser(getAdminUser()).withCoreAPI() + .usingNode(toContentModel(holdNodeRef)) + .listChildren().getEntries().stream() + .map(RestNodeModel::onModel) + .map(RestNodeModel::getName) + .filter(documentName -> documentName.equals(documentHeld.getName())) + .toList(); + + STEP("Check the list of active content"); + assertEquals(documentNames, Set.of(documentHeld.getName())); + } + + /** + * Given a hold that contains at least one active content + * When I use the existing REST API to retrieve the holds the content is added + * Then the hold where the content held is returned + */ + @Test + public void retrieveTheHoldWhereTheContentIsAdded() + { + RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory() + .getNodeAPI(documentHeld).usingParams("where=(assocType='rma:frozenContent')").getParents(); + Hold retrievedHold = getRestAPIFactory().getHoldsAPI(getAdminUser()) + .getHold(holdsEntries.getEntries().get(0).getModel().getId()); + assertEquals(retrievedHold, hold, "Holds are not equal"); + } + + /** + * Valid nodes to be added to hold + */ + @DataProvider(name = "validNodesForAddToHold") + public Object[][] getValidNodesForAddToHold() + { + //create electronic and nonElectronic record in record folder + RecordCategoryChild recordFolder = createCategoryFolderInFilePlan(); + RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI(); + nodesToBeClean.add(recordFolder.getParentId()); + Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(), + getFile + (IMAGE_FILE)); + assertStatusCode(CREATED); + + Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(), + recordFolder.getId()); + assertStatusCode(CREATED); + getRestAPIFactory().getRMUserAPI().addUserPermission(recordFolder.getId(), userAddHoldPermission, + PERMISSION_FILING); + + RecordCategoryChild folderToHold = createCategoryFolderInFilePlan(); + getRestAPIFactory().getRMUserAPI().addUserPermission(folderToHold.getId(), userAddHoldPermission, + PERMISSION_FILING); + nodesToBeClean.add(folderToHold.getParentId()); + + return new String[][] + { // record folder + { folderToHold.getId() }, + //electronic record + { electronicRecord.getId() }, + // non electronic record + { nonElectronicRecord.getId() }, + // document from collaboration site + { contentToAddToHold.getNodeRefWithoutVersion() }, + }; + } + + /** + * Given record folder/record/document not on hold + * And a hold + * And file permission on the hold + * And the appropriate capability to add to hold + * When I use the existing REST API to add the node to the hold + * Then the record folder/record/document is added to the hold + * And the item is frozen + * + * @throws Exception + */ + @Test(dataProvider = "validNodesForAddToHold") + public void addValidNodesToHoldWithAllowedUser(String nodeId) throws Exception + { + STEP("Add node to hold with user with permission."); + getRestAPIFactory().getHoldsAPI(userAddHoldPermission) + .addChildToHold(HoldChild.builder().id(nodeId).build(), hold.getId()); + + STEP("Check the node is frozen."); + assertTrue(hasAspect(nodeId, FROZEN_ASPECT)); + } + + /** + * Data provider with user without correct permission to add to hold and the node ref to be added to hold + * + * @return object with user model and the node ref to be added to hold + */ + @DataProvider(name = "userWithoutPermissionForAddToHold") + public Object[][] getUserWithoutPermissionForAddToHold() + { + //create record folder + RecordCategoryChild recordFolder = createCategoryFolderInFilePlan(); + //create a rm manager and grant read permission over the record folder created + UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId, + recordFolder.getId(), + PERMISSION_READ_RECORDS); + getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRef, user, PERMISSION_FILING); + nodesToBeClean.add(recordFolder.getParentId()); + return new Object[][] + { // user without write permission on the content + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer, + holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING), + contentAddToHoldNoPermission.getNodeRefWithoutVersion() + }, + // user with write permission on the content and without filling permission on a hold + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole + .SiteCollaborator, + holdNodeRef, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS), + contentAddToHoldNoPermission.getNodeRefWithoutVersion() + }, + // user with write permission on the content, filling permission on a hold without add to + // hold capability + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole + .SiteCollaborator, + holdNodeRef, UserRoles.ROLE_RM_POWER_USER, PERMISSION_READ_RECORDS), + contentAddToHoldNoPermission.getNodeRefWithoutVersion() + }, + //user without write permission on RM record folder + { + user, recordFolder.getId() + }, + + }; + } + + /** + * Given a node not on hold + * And a hold + * And user without right permission to add to hold + * When I use the existing REST API to add the node to the hold + * Then the node is not added to the hold + * And the node is not frozen + * + * @throws Exception + */ + @Test(dataProvider = "userWithoutPermissionForAddToHold") + public void addContentToHoldWithUserWithoutHoldPermission(UserModel userModel, String nodeToBeAddedToHold) + throws Exception + { + users.add(userModel); + STEP("Add the node to the hold with user without permission."); + + getRestAPIFactory() + .getHoldsAPI(userModel) + .addChildToHold(HoldChild.builder().id(nodeToBeAddedToHold).build(), holdNodeRef); + + assertStatusCode(FORBIDDEN); + getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE); + + STEP("Check the node is not frozen."); + assertFalse(hasAspect(nodeToBeAddedToHold, FROZEN_ASPECT)); + } + + /** + * Data provider with invalid node types that can be added to a hold + */ + @DataProvider(name = "invalidNodesForAddToHold") + public Object[][] getInvalidNodesForAddToHold() + { + //create locked file + FileModel contentLocked = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + + contentActions.checkOut(getAdminUser().getUsername(), getAdminUser().getPassword(), + testSite.getId(), contentLocked.getName()); + RecordCategory category = createRootCategory(getRandomAlphanumeric()); + nodesToBeClean.add(category.getId()); + return new Object[][] + { // file plan node id + { getFilePlan(FILE_PLAN_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE }, + //transfer container + { getTransferContainer(TRANSFERS_ALIAS).getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE }, + // a record category + { category.getId(), SC_BAD_REQUEST, INVALID_TYPE_ERROR_MESSAGE }, + // unfiled records root + { getUnfiledContainer(UNFILED_RECORDS_CONTAINER_ALIAS).getId(), SC_BAD_REQUEST, + INVALID_TYPE_ERROR_MESSAGE }, + // an arbitrary unfiled records folder + { createUnfiledContainerChild(UNFILED_RECORDS_CONTAINER_ALIAS, "Unfiled Folder " + + getRandomAlphanumeric(), UNFILED_RECORD_FOLDER_TYPE).getId(), SC_BAD_REQUEST, + INVALID_TYPE_ERROR_MESSAGE }, + //folder, + { dataContent.usingAdmin().usingSite(testSite).createFolder().getNodeRef(), SC_BAD_REQUEST, + INVALID_TYPE_ERROR_MESSAGE }, + //document locked + { contentLocked.getNodeRefWithoutVersion(), SC_BAD_REQUEST, LOCKED_FILE_ERROR_MESSAGE } + }; + } + + /** + * Given a node that is not a document/record/ record folder ( a valid node type to be added to hold) + * And a hold + * And user without right permission to add to hold + * When I use the existing REST API to add the node to the hold + * Then the node is not added to the hold + * And the node is not frozen + * + * @throws Exception + */ + @Test(dataProvider = "invalidNodesForAddToHold") + public void addInvalidNodesToHold(String itemNodeRef, int responseCode, String errorMessage) throws Exception + { + STEP("Add the node to the hold "); + + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(itemNodeRef).build(), holdNodeRef); + + assertStatusCode(HttpStatus.valueOf(responseCode)); + getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(errorMessage); + + STEP("Check node is not frozen."); + assertFalse(hasAspect(itemNodeRef, FROZEN_ASPECT)); + } + + private Hold createHold(String parentId, Hold hold, UserModel user) + { + FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user); + return filePlanAPI.createHold(hold, parentId); + } + + @AfterClass(alwaysRun = true) + public void cleanUpAddContentToHold() + { + getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdNodeRef); + dataSite.usingAdmin().deleteSite(testSite); + users.forEach(user -> getDataUser().usingAdmin().deleteUser(user)); + nodesToBeClean.forEach(this::deleteRecordCategory); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/HoldsTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/HoldsTests.java new file mode 100644 index 0000000000..661dc247ef --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/HoldsTests.java @@ -0,0 +1,186 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.NO_CONTENT; +import static org.springframework.http.HttpStatus.OK; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldDeletionReason; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +/** + * This class contains the tests for the Holds CRUD V1 API + * + * @author Damian Ujma + */ +public class HoldsTests extends BaseRMRestTest +{ + + private final List nodeRefs = new ArrayList<>(); + + @Test + public void testGetHold() + { + String holdName = "Hold" + getRandomAlphanumeric(); + String holdDescription = "Description" + getRandomAlphanumeric(); + String holdReason = "Reason" + getRandomAlphanumeric(); + + // Create the hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + + // Get the hold + Hold receivedHold = getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId()); + nodeRefs.add(receivedHold.getId()); + + // Verify the status code + assertStatusCode(OK); + + assertEquals(receivedHold.getName(), holdName); + assertEquals(receivedHold.getDescription(), holdDescription); + assertEquals(receivedHold.getReason(), holdReason); + assertNotNull(receivedHold.getId()); + } + + @Test + public void testUpdateHold() + { + String holdName = "Hold" + getRandomAlphanumeric(); + String holdDescription = "Description" + getRandomAlphanumeric(); + String holdReason = "Reason" + getRandomAlphanumeric(); + + // Create the hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + nodeRefs.add(createdHold.getId()); + + Hold holdModel = Hold.builder() + .name("Updated" + holdName) + .description("Updated" + holdDescription) + .reason("Updated" + holdReason) + .build(); + + // Update the hold + Hold updatedHold = getRestAPIFactory().getHoldsAPI().updateHold(holdModel, createdHold.getId()); + + // Verify the status code + assertStatusCode(OK); + + assertEquals(updatedHold.getName(), "Updated" + holdName); + assertEquals(updatedHold.getDescription(), "Updated" + holdDescription); + assertEquals(updatedHold.getReason(), "Updated" + holdReason); + assertNotNull(updatedHold.getId()); + } + + @Test + public void testDeleteHold() + { + String holdName = "Hold" + getRandomAlphanumeric(); + String holdDescription = "Description" + getRandomAlphanumeric(); + String holdReason = "Reason" + getRandomAlphanumeric(); + + // Create the hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + nodeRefs.add(createdHold.getId()); + + // Delete the hold + getRestAPIFactory().getHoldsAPI().deleteHold(createdHold.getId()); + + // Verify the status code + assertStatusCode(NO_CONTENT); + + // Try to get the hold + getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId()); + + // Verify the status code + assertStatusCode(NOT_FOUND); + } + + @Test + public void testDeleteHoldWithReason() + { + String holdName = "Hold" + getRandomAlphanumeric(); + String holdDescription = "Description" + getRandomAlphanumeric(); + String holdReason = "Reason" + getRandomAlphanumeric(); + + // Create the hold + Hold hold = Hold.builder() + .name(holdName) + .description(holdDescription) + .reason(holdReason) + .build(); + Hold createdHold = getRestAPIFactory().getFilePlansAPI() + .createHold(hold, FILE_PLAN_ALIAS); + nodeRefs.add(createdHold.getId()); + + // Delete the hold with the reason + getRestAPIFactory().getHoldsAPI() + .deleteHoldWithReason(HoldDeletionReason.builder().reason("Example reason").build(), createdHold.getId()); + + // Verify the status code + assertStatusCode(OK); + + // Try to get the hold + getRestAPIFactory().getHoldsAPI().getHold(createdHold.getId()); + + // Verify the status code + assertStatusCode(NOT_FOUND); + } + + @AfterClass(alwaysRun = true) + public void cleanUpHoldsTests() + { + nodeRefs.forEach(nodeRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(nodeRef)); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentV1Tests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentV1Tests.java new file mode 100644 index 0000000000..defad62b04 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/PreventActionsOnFrozenContentV1Tests.java @@ -0,0 +1,337 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; +import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.ASPECTS_VITAL_RECORD; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.ASPECTS_VITAL_RECORD_DEFINITION; +import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; +import static org.alfresco.rest.rm.community.utils.CoreUtil.createBodyForMoveCopy; +import static org.alfresco.utility.data.RandomData.getRandomName; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.apache.commons.httpclient.HttpStatus.SC_INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.OK; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.assertFalse; + +import java.io.File; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.alfresco.dataprep.CMISUtil; +import org.alfresco.rest.core.JsonBodyGenerator; +import org.alfresco.rest.core.v0.BaseAPI.RM_ACTIONS; +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.common.ReviewPeriod; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; +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.rm.community.model.recordfolder.RecordFolder; +import org.alfresco.rest.rm.community.model.recordfolder.RecordFolderProperties; +import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI; +import org.alfresco.rest.v0.RMRolesAndActionsAPI; +import org.alfresco.rest.v0.service.DispositionScheduleService; +import org.alfresco.utility.Utility; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.UserModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * V1 API tests to check actions on frozen content + * + * @author Damian Ujma + */ +public class PreventActionsOnFrozenContentV1Tests extends BaseRMRestTest +{ + private static String holdNodeRef; + private static FileModel contentHeld; + private static File updatedFile; + private static FolderModel folderModel; + private static RecordCategoryChild recordFolder; + private static Record recordFrozen; + private static Record recordNotHeld; + private static RecordCategory categoryWithRS; + + private Hold hold; + + @Autowired + private DispositionScheduleService dispositionScheduleService; + + @Autowired + private RMRolesAndActionsAPI rmRolesAndActionsAPI; + + @BeforeClass(alwaysRun = true) + public void preconditionForPreventActionsOnFrozenContent() + { + String holdOne = "HOLD" + generateTestPrefix(PreventActionsOnFrozenContentV1Tests.class); + + STEP("Create a hold."); + hold = createHold(FILE_PLAN_ALIAS, + Hold.builder().name(holdOne).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), getAdminUser()); + holdNodeRef = hold.getId(); + + STEP("Create a test file."); + testSite = dataSite.usingAdmin().createPublicRandomSite(); + contentHeld = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + + STEP("Add the file to the hold."); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(contentHeld.getNodeRefWithoutVersion()).build(), hold.getId()); + + STEP("Get a file resource."); + updatedFile = Utility.getResourceTestDataFile("SampleTextFile_10kb.txt"); + + STEP("Create a folder withing the test site ."); + folderModel = dataContent.usingAdmin().usingSite(testSite) + .createFolder(); + + STEP("Create a record folder with some records"); + recordFolder = createCategoryFolderInFilePlan(); + recordFrozen = createElectronicRecord(recordFolder.getId(), getRandomName("elRecordFrozen")); + recordNotHeld = createElectronicRecord(recordFolder.getId(), getRandomName("elRecordNotHeld")); + assertStatusCode(CREATED); + + STEP("Add the record to the hold."); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(recordFrozen.getId()).build(), hold.getId()); + } + + /** + * Given active content on hold + * When I try to edit the properties + * Or perform an action that edits the properties + * Then I am not successful + */ + @Test + public void editPropertiesForContentHeld() throws Exception + { + STEP("Update name property of the held content"); + JsonObject nameUpdated = Json.createObjectBuilder().add("name", "HeldNameUpdated").build(); + restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld) + .updateNode(nameUpdated.toString()); + + STEP("Check the request failed."); + restClient.assertStatusCodeIs(FORBIDDEN); + restClient.assertLastError().containsSummary("Frozen content can't be updated."); + } + + /* + * Given active content on hold + * When I try to update the content + * Then I am not successful + */ + @Test + public void updateContentForFrozenFile() throws Exception + { + STEP("Update content of the held file"); + restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld).updateNodeContent(updatedFile); + + STEP("Check the request failed."); + restClient.assertStatusCodeIs(INTERNAL_SERVER_ERROR); + restClient.assertLastError().containsSummary("Frozen content can't be updated."); + } + + /* + * Given active content on hold + * When I try to delete the content + * Then I am not successful + */ + @Test + public void deleteFrozenFile() throws Exception + { + STEP("Delete frozen file"); + restClient.authenticateUser(getAdminUser()).withCoreAPI().usingNode(contentHeld) + .deleteNode(contentHeld.getNodeRefWithoutVersion()); + + STEP("Check the request failed."); + restClient.assertStatusCodeIs(FORBIDDEN); + restClient.assertLastError().containsSummary("Frozen content can't be deleted."); + } + + /** + * Given active content on hold + * When I try to copy the content + * Then I am not successful + */ + @Test + public void copyFrozenFile() + { + STEP("Copy frozen file"); + String postBody = JsonBodyGenerator.keyValueJson("targetParentId", folderModel.getNodeRef()); + getRestAPIFactory().getNodeAPI(contentHeld).copyNode(postBody); + + STEP("Check the request failed."); + assertStatusCode(FORBIDDEN); + getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Permission was denied"); + } + + /** + * Given active content on hold + * When I try to move the content + * Then I am not successful + */ + @Test + public void moveFrozenFile() throws Exception + { + STEP("Move frozen file"); + getRestAPIFactory().getNodeAPI(contentHeld).move(createBodyForMoveCopy(folderModel.getNodeRef())); + + STEP("Check the request failed."); + assertStatusCode(FORBIDDEN); + getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary("Frozen content can't be moved."); + } + + /** + * Given a record folder with a frozen record and another record not held + * When I update the record folder and make the records as vital + * Then I am successful and the records not held are marked as vital + * And the frozen nodes have the vital record search properties updated + */ + @Test + public void updateRecordFolderVitalProperties() + { + STEP("Update the vital record properties for the record folder"); + // Create the record folder properties to update + RecordFolder recordFolderToUpdate = RecordFolder.builder() + .properties(RecordFolderProperties.builder() + .vitalRecordIndicator(true) + .reviewPeriod(new ReviewPeriod("month", "1")) + .build()) + .build(); + + // Update the record folder + RecordFolder updatedRecordFolder = getRestAPIFactory().getRecordFolderAPI().updateRecordFolder + (recordFolderToUpdate, + recordFolder.getId()); + assertStatusCode(OK); + assertTrue(updatedRecordFolder.getAspectNames().contains(ASPECTS_VITAL_RECORD_DEFINITION)); + + STEP("Check the frozen record was not marked as vital"); + recordFrozen = getRestAPIFactory().getRecordsAPI().getRecord(recordFrozen.getId()); + assertFalse(recordFrozen.getAspectNames().contains(ASPECTS_VITAL_RECORD)); + assertTrue(recordFrozen.getProperties().getRecordSearchVitalRecordReviewPeriod().contains("month")); + assertTrue(recordFrozen.getProperties().getRecordSearchVitalRecordReviewPeriodExpression().contains("1")); + + STEP("Check the record not held was marked as vital"); + recordNotHeld = getRestAPIFactory().getRecordsAPI().getRecord(recordNotHeld.getId()); + assertTrue(recordNotHeld.getAspectNames().contains(ASPECTS_VITAL_RECORD)); + assertNotNull(recordNotHeld.getProperties().getReviewAsOf()); + assertTrue(recordNotHeld.getProperties().getRecordSearchVitalRecordReviewPeriod().contains("month")); + assertTrue(recordNotHeld.getProperties().getRecordSearchVitalRecordReviewPeriodExpression().contains("1")); + } + + /** + * Given a record folder with a frozen record and another record not held + * When I add a disposition schedule + * Then I am successful + * And the record search disposition schedule properties are updated + */ + @Test + public void createDispositionScheduleOnCategoryWithHeldChildren() + { + STEP("Create a retention schedule on the category with frozen children"); + RecordCategory categoryWithRS = getRestAPIFactory().getRecordCategoryAPI() + .getRecordCategory(recordFolder.getParentId()); + dispositionScheduleService.createCategoryRetentionSchedule(categoryWithRS.getName(), false); + dispositionScheduleService.addCutOffImmediatelyStep(categoryWithRS.getName()); + dispositionScheduleService.addDestroyWithGhostingImmediatelyAfterCutOff(categoryWithRS.getName()); + + STEP("Check the record folder has a disposition schedule"); + RecordFolder folderWithRS = getRestAPIFactory().getRecordFolderAPI().getRecordFolder(recordFolder.getId()); + assertNotNull(folderWithRS.getProperties().getRecordSearchDispositionAuthority()); + assertNotNull(folderWithRS.getProperties().getRecordSearchDispositionInstructions()); + + } + + /** + * Given a record category with a disposition schedule applied to records + * And the disposition schedule has a retain step immediately and destroy step immediately + * And a complete record added to one hold + * When I execute the retain action + * Then the action is executed + * And the record search disposition schedule properties are updated + */ + @Test + public void retainActionOnFrozenHeldRecords() + { + STEP("Add a category with a disposition schedule."); + categoryWithRS = createRootCategory(getRandomName("CategoryWithRS")); + dispositionScheduleService.createCategoryRetentionSchedule(categoryWithRS.getName(), true); + dispositionScheduleService.addRetainAfterPeriodStep(categoryWithRS.getName(), "immediately"); + dispositionScheduleService.addDestroyWithGhostingImmediatelyAfterCutOff(categoryWithRS.getName()); + + STEP("Create record folder with a record."); + RecordCategoryChild folder = createFolder(categoryWithRS.getId(), getRandomName("RecFolder")); + Record record = createElectronicRecord(folder.getId(), getRandomName("elRecord")); + completeRecord(record.getId()); + + STEP("Add the record to the hold"); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(record.getId()).build(), hold.getId()); + + STEP("Execute the retain action"); + rmRolesAndActionsAPI.executeAction(getAdminUser().getUsername(), getAdminUser().getPassword(), record.getName(), + RM_ACTIONS.END_RETENTION, null, SC_INTERNAL_SERVER_ERROR); + + STEP("Check the record search disposition properties"); + Record recordUpdated = getRestAPIFactory().getRecordsAPI().getRecord(record.getId()); + assertTrue(recordUpdated.getProperties().getRecordSearchDispositionActionName() + .contains(RM_ACTIONS.END_RETENTION.getAction())); + assertTrue(recordUpdated.getProperties().getRecordSearchDispositionPeriod().contains("immediately")); + } + + private Hold createHold(String parentId, Hold hold, UserModel user) + { + FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user); + return filePlanAPI.createHold(hold, parentId); + } + + @AfterClass(alwaysRun = true) + public void cleanUpPreventActionsOnFrozenContent() + { + getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdNodeRef); + dataSite.usingAdmin().deleteSite(testSite); + deleteRecordCategory(recordFolder.getParentId()); + deleteRecordCategory(categoryWithRS.getId()); + } +} diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsTests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsTests.java index a48e9e16d3..8f317c2ff0 100644 --- a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsTests.java +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsTests.java @@ -52,7 +52,7 @@ import java.util.Set; import org.alfresco.dataprep.CMISUtil; import org.alfresco.rest.rm.community.base.BaseRMRestTest; -import org.alfresco.rest.rm.community.model.hold.HoldEntry; +import org.alfresco.rest.rm.community.model.hold.v0.HoldEntry; import org.alfresco.rest.rm.community.model.record.Record; import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; import org.alfresco.rest.rm.community.model.user.UserRoles; diff --git a/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsV1Tests.java b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsV1Tests.java new file mode 100644 index 0000000000..9f76269ce0 --- /dev/null +++ b/amps/ags/rm-automation/rm-automation-community-rest-api/src/test/java/org/alfresco/rest/rm/community/hold/RemoveFromHoldsV1Tests.java @@ -0,0 +1,374 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.hold; + +import static java.util.Arrays.asList; + +import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION; +import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS; +import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAspects.FROZEN_ASPECT; +import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING; +import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS; +import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER; +import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.IMAGE_FILE; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createElectronicRecordModel; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.createNonElectronicRecordModel; +import static org.alfresco.rest.rm.community.utils.FilePlanComponentsUtil.getFile; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.alfresco.dataprep.CMISUtil; +import org.alfresco.rest.model.RestNodeAssociationModelCollection; +import org.alfresco.rest.rm.community.base.BaseRMRestTest; +import org.alfresco.rest.rm.community.model.hold.Hold; +import org.alfresco.rest.rm.community.model.hold.HoldChild; +import org.alfresco.rest.rm.community.model.record.Record; +import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryChild; +import org.alfresco.rest.rm.community.model.user.UserRoles; +import org.alfresco.rest.rm.community.requests.gscore.api.FilePlanAPI; +import org.alfresco.rest.rm.community.requests.gscore.api.RecordFolderAPI; +import org.alfresco.rest.rm.community.utils.CoreUtil; +import org.alfresco.rest.v0.service.RoleService; +import org.alfresco.utility.constants.UserRole; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.UserModel; +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; + +/** + * V1 API tests for removing content/record folder/record from holds + * + * @author Damian Ujma + */ +public class RemoveFromHoldsV1Tests extends BaseRMRestTest +{ + private static final String HOLD_ONE = "HOLD_ONE" + generateTestPrefix(RemoveFromHoldsV1Tests.class); + private static final String HOLD_TWO = "HOLD_TWO" + generateTestPrefix(RemoveFromHoldsV1Tests.class); + private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " + + "permissions to perform this operation."; + + private SiteModel testSite; + private SiteModel privateSite; + private String holdNodeRefOne; + private FileModel contentHeld; + private FileModel contentAddToManyHolds; + private List holdsListRef = new ArrayList<>(); + private final Set usersToBeClean = new HashSet<>(); + private final Set nodesToBeClean = new HashSet<>(); + + @Autowired + private RoleService roleService; + + @BeforeClass(alwaysRun = true) + public void preconditionForRemoveContentFromHold() + { + STEP("Create two holds."); + + holdNodeRefOne = createHold(FILE_PLAN_ALIAS, + Hold.builder().name(HOLD_ONE).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), + getAdminUser()).getId(); + String holdNodeRefTwo = createHold(FILE_PLAN_ALIAS, + Hold.builder().name(HOLD_TWO).description(HOLD_DESCRIPTION).reason(HOLD_REASON).build(), + getAdminUser()).getId(); + holdsListRef = asList(holdNodeRefOne, holdNodeRefTwo); + + STEP("Create test files."); + testSite = dataSite.usingAdmin().createPublicRandomSite(); + privateSite = dataSite.usingAdmin().createPrivateRandomSite(); + contentHeld = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + contentAddToManyHolds = dataContent.usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + + STEP("Add content to the holds."); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(contentHeld.getNodeRefWithoutVersion()).build(), holdNodeRefOne); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(contentAddToManyHolds.getNodeRefWithoutVersion()).build(), + holdNodeRefOne); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(contentAddToManyHolds.getNodeRefWithoutVersion()).build(), + holdNodeRefTwo); + } + + /** + * Valid nodes to be removed from hold + */ + @DataProvider(name = "validNodesToRemoveFromHold") + public Object[][] getValidNodesToRemoveFromHold() + { + //create electronic and nonElectronic record in record folder + RecordCategoryChild recordFolder = createCategoryFolderInFilePlan(); + RecordFolderAPI recordFolderAPI = getRestAPIFactory().getRecordFolderAPI(); + nodesToBeClean.add(recordFolder.getParentId()); + Record electronicRecord = recordFolderAPI.createRecord(createElectronicRecordModel(), recordFolder.getId(), + getFile + (IMAGE_FILE)); + assertStatusCode(CREATED); + Record nonElectronicRecord = recordFolderAPI.createRecord(createNonElectronicRecordModel(), + recordFolder.getId()); + assertStatusCode(CREATED); + + RecordCategoryChild folderToHeld = createCategoryFolderInFilePlan(); + nodesToBeClean.add(folderToHeld.getParentId()); + Stream.of(electronicRecord.getId(), nonElectronicRecord.getId(), folderToHeld.getId()) + .forEach(id -> getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne)); + + return new String[][] + { // record folder + { folderToHeld.getId() }, + //electronic record + { electronicRecord.getId() }, + // non electronic record + { nonElectronicRecord.getId() }, + // document from collaboration site + { contentHeld.getNodeRefWithoutVersion() }, + }; + } + + /** + * Given content/record folder/record that is held + * And the corresponding hold + * When I use the existing REST API to remove the node from the hold + * Then the node is removed from the hold + * And is no longer frozen + */ + @Test(dataProvider = "validNodesToRemoveFromHold") + public void removeContentFromHold(String nodeId) throws Exception + { + STEP("Remove node from hold"); + getRestAPIFactory() + .getHoldsAPI(getAdminUser()).deleteHoldChild(holdNodeRefOne, nodeId); + + STEP("Check the node is not held"); + assertFalse(hasAspect(nodeId, FROZEN_ASPECT)); + + STEP("Check node is not in any hold"); + RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory() + .getNodeAPI(CoreUtil.toContentModel(nodeId)).usingParams("where=(assocType='rma:frozenContent')") + .getParents(); + assertTrue(holdsEntries.getEntries().isEmpty(), "Content held is still added to a hold."); + } + + /** + * Given active content that is held on many holds + * When I use the existing REST API to remove the active content from one hold + * Then the active content is removed from the specific hold + * And is frozen + * And in the other holds + */ + @Test + public void removeContentAddedToManyHolds() throws Exception + { + STEP("Remove content from hold. "); + + getRestAPIFactory().getHoldsAPI(getAdminUser()) + .deleteHoldChild(holdNodeRefOne, contentAddToManyHolds.getNodeRefWithoutVersion()); + + STEP("Check the content is held. "); + assertTrue(hasAspect(contentAddToManyHolds.getNodeRefWithoutVersion(), FROZEN_ASPECT)); + + STEP("Check node is in hold HOLD_TWO. "); + + RestNodeAssociationModelCollection holdsEntries = getRestAPIFactory() + .getNodeAPI(CoreUtil.toContentModel(contentAddToManyHolds.getNodeRefWithoutVersion())) + .usingParams("where=(assocType='rma:frozenContent')").getParents(); + assertFalse(holdsEntries.getEntries().isEmpty(), "Content held is not held after removing from one hold."); + assertTrue(holdsEntries.getEntries().stream() + .anyMatch(restNodeModel -> restNodeModel.getModel().getName().equals(HOLD_TWO)), + "Content held is not held after removing from one hold."); + } + + /** + * Data provider with user without right permission or capability to remove from hold a specific node + * + * @return user model and the node ref to be removed from hold + */ + @DataProvider(name = "userWithoutPermissionForRemoveFromHold") + public Object[][] getUserWithoutPermissionForAddToHold() + { + //create record folder + RecordCategoryChild recordFolder = createCategoryFolderInFilePlan(); + nodesToBeClean.add(recordFolder.getParentId()); + UserModel user = roleService.createUserWithRMRole(ROLE_RM_MANAGER.roleId); + getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING); + //create files that will be removed from hold + FileModel contentNoHoldPerm = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + FileModel contentNoHoldCap = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + FileModel privateFile = dataContent.usingAdmin().usingSite(privateSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + //add files to hold + asList(recordFolder.getId(), contentNoHoldCap.getNodeRefWithoutVersion(), + contentNoHoldPerm.getNodeRefWithoutVersion(), privateFile.getNodeRefWithoutVersion()) + .forEach(id -> getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne)); + + return new Object[][] + { + // user with read permission on the content, with remove from hold capability and without + // filling permission on a hold + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteCollaborator, + holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS), + contentNoHoldPerm.getNodeRefWithoutVersion() + }, + // user with write permission on the content, filling permission on a hold without remove from + // hold capability + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole + .SiteCollaborator, + holdNodeRefOne, UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING), + contentNoHoldCap.getNodeRefWithoutVersion() + }, + //user without read permission on RM record folder + { + user, recordFolder.getId() + }, + //user without read permission over the content from the private site + { + user, privateFile.getNodeRefWithoutVersion() + } + }; + } + + /** + * Given node on hold in a single hold location + * And the user does not have sufficient permissions or capabilities to remove the node from the hold + * When the user tries to remove the node from the hold + * Then it's unsuccessful + * + * @throws Exception + */ + @Test(dataProvider = "userWithoutPermissionForRemoveFromHold") + public void removeFromHoldWithUserWithoutPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception + { + STEP("Update the list of users to be deleted after running the tests"); + usersToBeClean.add(userModel); + + STEP("Remove node from hold with user without right permission or capability"); + getRestAPIFactory().getHoldsAPI(userModel).deleteHoldChild(holdNodeRefOne, nodeIdToBeRemoved); + + assertStatusCode(FORBIDDEN); + getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE); + + STEP("Check node is frozen."); + assertTrue(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT)); + } + + /** + * Data provider with user with right permission or capability to remove from hold a specific node + * + * @return user model and the node ref to be removed from hold + */ + @DataProvider(name = "userWithPermissionForRemoveFromHold") + public Object[][] getUserWithPermissionForAddToHold() + { + //create record folder + RecordCategoryChild recordFolder = createCategoryFolderInFilePlan(); + nodesToBeClean.add(recordFolder.getParentId()); + UserModel user = roleService.createUserWithRMRoleAndRMNodePermission(ROLE_RM_MANAGER.roleId, + recordFolder.getId(), + PERMISSION_READ_RECORDS); + getRestAPIFactory().getRMUserAPI().addUserPermission(holdNodeRefOne, user, PERMISSION_FILING); + //create file that will be removed from hold + FileModel contentPermission = dataContent.usingAdmin().usingSite(testSite) + .createContent(CMISUtil.DocumentType.TEXT_PLAIN); + + //add files to hold + asList(recordFolder.getId(), contentPermission.getNodeRefWithoutVersion()) + .forEach(id -> getRestAPIFactory() + .getHoldsAPI(getAdminUser()) + .addChildToHold(HoldChild.builder().id(id).build(), holdNodeRefOne)); + + return new Object[][] + { + // user with write permission on the content + { + roleService.createUserWithSiteRoleRMRoleAndPermission(testSite, UserRole.SiteConsumer, + holdNodeRefOne, UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING), + contentPermission.getNodeRefWithoutVersion() + }, + //user with read permission on RM record folder + { + user, recordFolder.getId() + }, + + }; + } + + @Test(dataProvider = "userWithPermissionForRemoveFromHold") + public void removeFromHoldWithUserWithPermission(UserModel userModel, String nodeIdToBeRemoved) throws Exception + { + STEP("Update the list of users to be deleted after running the tests"); + usersToBeClean.add(userModel); + + STEP("Remove node from hold with user with right permission and capability"); + getRestAPIFactory().getHoldsAPI(userModel).deleteHoldChild(holdNodeRefOne, nodeIdToBeRemoved); + + STEP("Check node is not frozen."); + assertFalse(hasAspect(nodeIdToBeRemoved, FROZEN_ASPECT)); + } + + private Hold createHold(String parentId, Hold hold, UserModel user) + { + FilePlanAPI filePlanAPI = getRestAPIFactory().getFilePlansAPI(user); + return filePlanAPI.createHold(hold, parentId); + } + + @AfterClass(alwaysRun = true) + public void cleanUpRemoveContentFromHold() + { + holdsListRef.forEach(holdRef -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(holdRef)); + dataSite.usingAdmin().deleteSite(testSite); + dataSite.usingAdmin().deleteSite(privateSite); + usersToBeClean.forEach(user -> getDataUser().usingAdmin().deleteUser(user)); + nodesToBeClean.forEach(this::deleteRecordCategory); + } +} diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index a667c393da..368492e785 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -538,6 +538,11 @@ d:text true + + Hold Deletion Reason + d:text + false + diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml index b711326ba6..f969a458ea 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-public-rest-context.xml @@ -69,7 +69,32 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index ae80740b66..dfc436146d 100644 --- a/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/amps/ags/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -1614,6 +1614,8 @@ org.alfresco.module.org_alfresco_module_rm.hold.HoldService.createHold=RM_CAP.0.rma:filePlanComponent.CreateHold org.alfresco.module.org_alfresco_module_rm.hold.HoldService.getHoldReason=RM.Read.0 org.alfresco.module.org_alfresco_module_rm.hold.HoldService.setHoldReason=RM_CAP.0.rma:filePlanComponent.EditHold + org.alfresco.module.org_alfresco_module_rm.hold.HoldService.setHoldDeletionReason=RM_CAP.0.rma:filePlanComponent.EditHold + org.alfresco.module.org_alfresco_module_rm.hold.HoldService.updateHold=RM_CAP.0.rma:filePlanComponent.EditHold org.alfresco.module.org_alfresco_module_rm.hold.HoldService.deleteHold=RM_CAP.0.rma:filePlanComponent.DeleteHold org.alfresco.module.org_alfresco_module_rm.hold.HoldService.addToHold=RM_CAP.0.rma:filePlanComponent.AddToHold org.alfresco.module.org_alfresco_module_rm.hold.HoldService.addToHolds=RM_ALLOW diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteHoldAuditEvent.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteHoldAuditEvent.java index 01cb1063f6..faa2273808 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteHoldAuditEvent.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/DeleteHoldAuditEvent.java @@ -27,6 +27,7 @@ package org.alfresco.module.org_alfresco_module_rm.audit.event; +import static org.alfresco.module.org_alfresco_module_rm.audit.event.HoldUtils.HOLD_DELETION_REASON; import static org.alfresco.repo.policy.Behaviour.NotificationFrequency.EVERY_EVENT; import java.io.Serializable; @@ -77,6 +78,8 @@ public class DeleteHoldAuditEvent extends AuditEvent implements NodeServicePolic public void beforeDeleteNode(NodeRef holdNodeRef) { Map auditProperties = HoldUtils.makePropertiesMap(holdNodeRef, nodeService); + auditProperties.put(HOLD_DELETION_REASON, nodeService.getProperty(holdNodeRef, PROP_HOLD_DELETION_REASON)); + recordsManagementAuditService.auditEvent(holdNodeRef, getName(), auditProperties, null, true, false); } } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/HoldUtils.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/HoldUtils.java index f293a593be..65e4732e08 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/HoldUtils.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/audit/event/HoldUtils.java @@ -47,6 +47,7 @@ class HoldUtils { /** A QName to display for the hold name. */ public static final QName HOLD_NAME = QName.createQName(RecordsManagementModel.RM_URI, "Hold Name"); + public static final QName HOLD_DELETION_REASON = QName.createQName(RecordsManagementModel.RM_URI, "Hold deletion reason"); /** A QName to display for the hold node ref. */ public static final QName HOLD_NODEREF = QName.createQName(RecordsManagementModel.RM_URI, "Hold NodeRef"); diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldService.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldService.java index eb80e730a2..ef392b03bf 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldService.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldService.java @@ -111,6 +111,24 @@ public interface HoldService */ void setHoldReason(NodeRef hold, String reason); + /** + * Sets the reason for the hold deletion + * + * @param hold The {@link NodeRef} of the hold + * @param reason {@link String} The reason for the hold + */ + void setHoldDeletionReason(NodeRef hold, String reason); + + /** + * Updates a hold with the given name, reason and description + * + * @param hold The {@link NodeRef} of the hold + * @param name {@link String} The name of the hold + * @param reason {@link String} The reason of the hold + * @param description {@link String} The description of the hold + */ + void updateHold(NodeRef hold, String name, String reason, String description); + /** * Deletes the hold * diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java index 636eb2101d..1bcc1f9cf4 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java @@ -29,6 +29,7 @@ package org.alfresco.module.org_alfresco_module_rm.hold; import static org.alfresco.model.ContentModel.ASPECT_LOCKABLE; import static org.alfresco.model.ContentModel.ASSOC_CONTAINS; +import static org.alfresco.model.ContentModel.PROP_DESCRIPTION; import static org.alfresco.model.ContentModel.PROP_NAME; import java.io.Serializable; @@ -458,11 +459,11 @@ public class HoldServiceImpl extends ServiceBaseImpl // create map of properties Map properties = new HashMap<>(3); - properties.put(ContentModel.PROP_NAME, name); + properties.put(PROP_NAME, name); properties.put(PROP_HOLD_REASON, reason); if (description != null && !description.isEmpty()) { - properties.put(ContentModel.PROP_DESCRIPTION, description); + properties.put(PROP_DESCRIPTION, description); } // create assoc name @@ -512,6 +513,39 @@ public class HoldServiceImpl extends ServiceBaseImpl } } + /** + * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#setHoldDeletionReason(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + public void setHoldDeletionReason(NodeRef hold, String reason) + { + ParameterCheck.mandatory("hold", hold); + ParameterCheck.mandatory("reason", reason); + + if (nodeService.exists(hold) && isHold(hold)) + { + nodeService.setProperty(hold, PROP_HOLD_DELETION_REASON, reason); + } + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#updateHold(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, java.lang.String) (org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void updateHold(NodeRef hold, String name, String reason, String description) + { + ParameterCheck.mandatory("hold", hold); + ParameterCheck.mandatory("name", name); + ParameterCheck.mandatory("reason", reason); + + if (nodeService.exists(hold) && isHold(hold)) + { + nodeService.setProperty(hold, PROP_NAME, name); + nodeService.setProperty(hold, PROP_HOLD_REASON, reason); + nodeService.setProperty(hold, PROP_DESCRIPTION, description); + } + } + /** * @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#deleteHold(org.alfresco.service.cmr.repository.NodeRef) */ @@ -563,7 +597,7 @@ public class HoldServiceImpl extends ServiceBaseImpl if (permissionService.hasPermission(nodeRef, permission) == AccessStatus.DENIED) { - heldNames.add((String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME)); + heldNames.add((String) nodeService.getProperty(nodeRef, PROP_NAME)); } } catch (AccessDeniedException ade) @@ -630,7 +664,7 @@ public class HoldServiceImpl extends ServiceBaseImpl { if (!isHold(hold)) { - final String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME); + final String holdName = (String) nodeService.getProperty(hold, PROP_NAME); throw new IntegrityException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null); } @@ -688,7 +722,7 @@ public class HoldServiceImpl extends ServiceBaseImpl { if (!isRecordFolder(nodeRef) && !instanceOf(nodeRef, ContentModel.TYPE_CONTENT)) { - final String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + final String nodeName = (String) nodeService.getProperty(nodeRef, PROP_NAME); throw new IntegrityException(I18NUtil.getMessage("rm.hold.add-to-hold-invalid-type", nodeName), null); } @@ -795,7 +829,7 @@ public class HoldServiceImpl extends ServiceBaseImpl { if (!isHold(hold)) { - final String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME); + final String holdName = (String) nodeService.getProperty(hold, PROP_NAME); throw new IntegrityException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null); } diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java index 51d20f93c7..8386e949fa 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java @@ -35,6 +35,7 @@ import org.alfresco.service.namespace.QName; * * @author Roy Wetherall */ +@SuppressWarnings("PMD.ConstantsInInterface") @AlfrescoPublicApi public interface RecordsManagementModel extends RecordsManagementCustomModel { @@ -200,6 +201,7 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel // Hold type QName TYPE_HOLD = QName.createQName(RM_URI, "hold"); QName PROP_HOLD_REASON = QName.createQName(RM_URI, "holdReason"); + QName PROP_HOLD_DELETION_REASON = QName.createQName(RM_URI, "holdDeletionReason"); //since 3.2 @Deprecated QName ASSOC_FROZEN_RECORDS = QName.createQName(RM_URI, "frozenRecords"); diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanHoldsRelation.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanHoldsRelation.java new file mode 100644 index 0000000000..cc92610176 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/fileplans/FilePlanHoldsRelation.java @@ -0,0 +1,155 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.fileplans; + +import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; +import static org.alfresco.util.ParameterCheck.mandatory; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory; +import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils; +import org.alfresco.rm.rest.api.model.HoldModel; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.beans.factory.InitializingBean; + +/** + * File plan holds relation + * + * @author Damian Ujma + */ +@RelationshipResource(name = "holds", entityResource = FilePlanEntityResource.class, title = "Holds in a file plan") +public class FilePlanHoldsRelation implements + RelationshipResourceAction.Create, + RelationshipResourceAction.Read, + InitializingBean +{ + private FilePlanComponentsApiUtils apiUtils; + private ApiNodesModelFactory nodesModelFactory; + private HoldService holdService; + private FileFolderService fileFolderService; + private TransactionService transactionService; + + @Override + public void afterPropertiesSet() throws Exception + { + mandatory("apiUtils", this.apiUtils); + mandatory("nodesModelFactory", this.nodesModelFactory); + mandatory("holdService", this.holdService); + mandatory("fileFolderService", this.fileFolderService); + mandatory("transactionService", this.transactionService); + } + + @Override + @WebApiDescription(title = "Return a paged list of holds for the file plan identified by 'filePlanId'") + public CollectionWithPagingInfo readAll(String filePlanId, Parameters parameters) + { + checkNotBlank("filePlanId", filePlanId); + mandatory("parameters", parameters); + + NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN); + List holds = holdService.getHolds(parentNodeRef); + + List page = holds.stream() + .map(hold -> fileFolderService.getFileInfo(hold)) + .map(nodesModelFactory::createHoldModel) + .skip(parameters.getPaging().getSkipCount()) + .limit(parameters.getPaging().getMaxItems()) + .collect(Collectors.toCollection(LinkedList::new)); + + int totalItems = holds.size(); + boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems; + return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems); + } + + @Override + @WebApiDescription(title = "Create one (or more) holds in a file plan identified by 'filePlanId'") + public List create(String filePlanId, List holds, Parameters parameters) + { + checkNotBlank("filePlanId", filePlanId); + mandatory("holds", holds); + mandatory("parameters", parameters); + + NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN); + + RetryingTransactionCallback> callback = () -> { + List createdNodes = new LinkedList<>(); + for (HoldModel nodeInfo : holds) + { + NodeRef newNodeRef = holdService.createHold(parentNodeRef, nodeInfo.name(), nodeInfo.reason(), + nodeInfo.description()); + createdNodes.add(newNodeRef); + } + return createdNodes; + }; + + List createdNodes = transactionService.getRetryingTransactionHelper() + .doInTransaction(callback, false, true); + + return createdNodes.stream() + .map(hold -> fileFolderService.getFileInfo(hold)) + .map(nodesModelFactory::createHoldModel) + .collect(Collectors.toCollection(LinkedList::new)); + } + + public void setApiUtils(FilePlanComponentsApiUtils apiUtils) + { + this.apiUtils = apiUtils; + } + + public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) + { + this.nodesModelFactory = nodesModelFactory; + } + + public void setHoldService(HoldService holdService) + { + this.holdService = holdService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsChildrenRelation.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsChildrenRelation.java new file mode 100644 index 0000000000..753b05471b --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsChildrenRelation.java @@ -0,0 +1,207 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.holds; + +import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; +import static org.alfresco.util.ParameterCheck.mandatory; + +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.node.integrity.IntegrityException; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory; +import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils; +import org.alfresco.rm.rest.api.model.HoldChild; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Hold children relation + * + * @author Damian Ujma + */ +@RelationshipResource(name = "children", entityResource = HoldsEntityResource.class, title = "Children of a hold") +public class HoldsChildrenRelation implements + RelationshipResourceAction.Create, + RelationshipResourceAction.Read, + RelationshipResourceAction.Delete, + InitializingBean +{ + private HoldService holdService; + private FilePlanComponentsApiUtils apiUtils; + private ApiNodesModelFactory nodesModelFactory; + private TransactionService transactionService; + private FileFolderService fileFolderService; + private PermissionService permissionService; + + @Override + public void afterPropertiesSet() throws Exception + { + mandatory("holdService", holdService); + mandatory("apiUtils", apiUtils); + mandatory("nodesModelFactory", nodesModelFactory); + mandatory("transactionService", transactionService); + mandatory("fileFolderService", fileFolderService); + } + + @Override + @WebApiDescription(title = "Add one (or more) children as children of a hold identified by 'holdId'") + public List create(String holdId, List children, Parameters parameters) + { + // validate parameters + checkNotBlank("holdId", holdId); + mandatory("children", children); + mandatory("parameters", parameters); + + NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + + RetryingTransactionCallback> callback = () -> { + List createdNodes = children.stream() + .map(holdChild -> new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, holdChild.id())) + .collect(Collectors.toList()); + try + { + holdService.addToHold(parentNodeRef, createdNodes); + } + catch (IntegrityException exception) + { + // Throw 400 Bad Request when a node with id 'holdId' is not a hold or a child cannot be added to a hold + throw new InvalidArgumentException(exception.getMsgId()).initCause(exception); + } + return createdNodes; + }; + + List nodeInfos = transactionService.getRetryingTransactionHelper() + .doInTransaction(callback, false, true); + + return nodeInfos.stream() + .map(nodeRef -> new HoldChild(nodeRef.getId())) + .collect(Collectors.toCollection(LinkedList::new)); + } + + @Override + @WebApiDescription(title = "Return a paged list of hold children for the hold identified by 'holdId'") + public CollectionWithPagingInfo readAll(String holdId, Parameters parameters) + { + checkNotBlank("holdId", holdId); + mandatory("parameters", parameters); + + NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + List children = holdService.getHeld(parentNodeRef); + + List page = children.stream() + .map(NodeRef::getId) + .map(HoldChild::new) + .skip(parameters.getPaging().getSkipCount()) + .limit(parameters.getPaging().getMaxItems()) + .collect(Collectors.toCollection(LinkedList::new)); + + int totalItems = children.size(); + boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems; + return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems); + + } + + @Override + @WebApiDescription(title = "Remove a child from a hold", description = "Remove a child with id 'childId' from a hold with id 'holdId'") + public void delete(String holdId, String childId, Parameters parameters) + { + checkNotBlank("holdId", holdId); + checkNotBlank("childId", childId); + mandatory("parameters", parameters); + + NodeRef nodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + NodeRef childRef = apiUtils.lookupByPlaceholder(childId); + + if (permissionService.hasReadPermission(childRef) == AccessStatus.DENIED) + { + throw new PermissionDeniedException(I18NUtil.getMessage("permissions.err_access_denied")); + } + + RetryingTransactionCallback> callback = () -> { + try + { + holdService.removeFromHold(nodeRef, childRef); + } + catch (IntegrityException exception) + { + // Throw 400 Bad Request when a node with id 'holdId' is not a hold + throw new InvalidArgumentException(exception.getMsgId()).initCause(exception); + } + return null; + }; + + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + } + + public void setHoldService(HoldService holdService) + { + this.holdService = holdService; + } + + public void setApiUtils(FilePlanComponentsApiUtils apiUtils) + { + this.apiUtils = apiUtils; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) + { + this.nodesModelFactory = nodesModelFactory; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsEntityResource.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsEntityResource.java new file mode 100644 index 0000000000..2e8cdf9231 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/HoldsEntityResource.java @@ -0,0 +1,184 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.holds; + +import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; +import static org.alfresco.util.ParameterCheck.mandatory; + +import jakarta.servlet.http.HttpServletResponse; +import org.alfresco.module.org_alfresco_module_rm.hold.HoldService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.rest.framework.Operation; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory; +import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils; +import org.alfresco.rm.rest.api.model.HoldDeletionReason; +import org.alfresco.rm.rest.api.model.HoldModel; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.InitializingBean; + +/** + * Hold entity resource + * + * @author Damian Ujma + */ +@EntityResource(name = "holds", title = "Holds") +public class HoldsEntityResource implements + EntityResourceAction.ReadById, + EntityResourceAction.Update, + EntityResourceAction.Delete, + InitializingBean +{ + private FilePlanComponentsApiUtils apiUtils; + private FileFolderService fileFolderService; + private ApiNodesModelFactory nodesModelFactory; + private HoldService holdService; + private TransactionService transactionService; + + @Override + public void afterPropertiesSet() throws Exception + { + mandatory("nodesModelFactory", nodesModelFactory); + mandatory("apiUtils", apiUtils); + mandatory("fileFolderService", fileFolderService); + mandatory("holdService", holdService); + mandatory("transactionService", transactionService); + } + + @Override + @WebApiDescription(title = "Get hold information", description = "Get information for a hold with id 'holdId'") + @WebApiParam(name = "holdId", title = "The hold id") + public HoldModel readById(String holdId, Parameters parameters) + { + checkNotBlank("holdId", holdId); + mandatory("parameters", parameters); + + NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + FileInfo info = fileFolderService.getFileInfo(hold); + return nodesModelFactory.createHoldModel(info); + } + + @Override + @WebApiDescription(title = "Update a hold", description = "Updates a hold with id 'holdId'") + public HoldModel update(String holdId, HoldModel holdModel, Parameters parameters) + { + checkNotBlank("holdId", holdId); + mandatory("holdModel", holdModel); + mandatory("holdModel.name", holdModel.name()); + mandatory("holdModel.reason", holdModel.reason()); + mandatory("parameters", parameters); + + NodeRef nodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + + RetryingTransactionCallback callback = () -> { + holdService.updateHold(nodeRef, holdModel.name(), holdModel.reason(), holdModel.description()); + return null; + }; + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + + RetryingTransactionCallback readCallback = () -> fileFolderService.getFileInfo(nodeRef); + FileInfo info = transactionService.getRetryingTransactionHelper().doInTransaction(readCallback, false, true); + + return nodesModelFactory.createHoldModel(info); + } + + @Override + @WebApiDescription(title = "Delete hold", description = "Deletes a hold with id 'holdId'") + public void delete(String holdId, Parameters parameters) + { + checkNotBlank("holdId", holdId); + mandatory("parameters", parameters); + + NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + RetryingTransactionCallback callback = () -> { + holdService.deleteHold(hold); + return null; + }; + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + } + + @Operation("delete") + @WebApiDescription(title = "Delete hold with a reason", + successStatus = HttpServletResponse.SC_OK) + public HoldDeletionReason deleteHoldWithReason(String holdId, HoldDeletionReason reason, Parameters parameters, + WithResponse withResponse) + { + checkNotBlank("holdId", holdId); + mandatory("reason", reason); + mandatory("parameters", parameters); + + NodeRef hold = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD); + String deletionReason = reason.reason(); + + RetryingTransactionCallback callback = () -> { + if (StringUtils.isNotBlank(deletionReason)) + { + holdService.setHoldDeletionReason(hold, deletionReason); + } + holdService.deleteHold(hold); + return null; + }; + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + + return reason; + } + + public void setApiUtils(FilePlanComponentsApiUtils apiUtils) + { + this.apiUtils = apiUtils; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory) + { + this.nodesModelFactory = nodesModelFactory; + } + + public void setHoldService(HoldService holdService) + { + this.holdService = holdService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/package-info.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/package-info.java new file mode 100644 index 0000000000..905b0fc480 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/holds/package-info.java @@ -0,0 +1,36 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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 info that defines the Information Governance Holds REST API + * + * @author Damian Ujma + */ +@WebApi(name="gs", scope=Api.SCOPE.PUBLIC, version=1) +package org.alfresco.rm.rest.api.holds; +import org.alfresco.rest.framework.Api; +import org.alfresco.rest.framework.WebApi; \ No newline at end of file diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java index ef22c88ee3..a48a7ae4cf 100644 --- a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/impl/ApiNodesModelFactory.java @@ -47,6 +47,7 @@ import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rm.rest.api.model.FilePlan; +import org.alfresco.rm.rest.api.model.HoldModel; import org.alfresco.rm.rest.api.model.RMNode; import org.alfresco.rm.rest.api.model.Record; import org.alfresco.rm.rest.api.model.RecordCategory; @@ -637,6 +638,21 @@ public class ApiNodesModelFactory } } + + /** + * Creates an object of type HoldModel + * + * @param info info of the hold + * @return HoldModel object + */ + public HoldModel createHoldModel(FileInfo info) + { + return new HoldModel(info.getNodeRef().getId(), + (String) info.getProperties().get(ContentModel.PROP_NAME), + (String) info.getProperties().get(ContentModel.PROP_DESCRIPTION), + (String) info.getProperties().get(RecordsManagementModel.PROP_HOLD_REASON)); + } + /** * Creates an object of type FilePlan * diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldChild.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldChild.java new file mode 100644 index 0000000000..30aa86bf3a --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldChild.java @@ -0,0 +1,36 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.model; + +/** + * Hold Child POJO for use in the v1 REST API. + * + * @author Damian Ujma + */ +public record HoldChild(String id) +{ +} \ No newline at end of file diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldDeletionReason.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldDeletionReason.java new file mode 100644 index 0000000000..c0d73f58b5 --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldDeletionReason.java @@ -0,0 +1,36 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.model; + +/** + * Hold Deletion Reason POJO for use in the v1 REST API. + * + * @author Damian Ujma + */ +public record HoldDeletionReason(String reason) +{ +} diff --git a/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldModel.java b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldModel.java new file mode 100644 index 0000000000..f3a11cf84e --- /dev/null +++ b/amps/ags/rm-community/rm-community-repo/source/java/org/alfresco/rm/rest/api/model/HoldModel.java @@ -0,0 +1,36 @@ +/*- + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2024 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.model; + +/** + * Hold POJO for use in the v1 REST API. + * + * @author Damian Ujma + */ +public record HoldModel(String id, String name, String description, String reason) +{ +} diff --git a/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImplUnitTest.java b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImplUnitTest.java index 03b61adbce..8ac6fece18 100644 --- a/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImplUnitTest.java +++ b/amps/ags/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImplUnitTest.java @@ -87,6 +87,7 @@ public class HoldServiceImplUnitTest extends BaseUnitTest /** test values */ private static final String HOLD_NAME = "holdname"; private static final String HOLD_REASON = "holdreason"; + private static final String HOLD_DELETION_REASON = "holddeletionreason"; private static final String HOLD_DESCRIPTION = "holddescription"; private static final String GENERIC_ERROR_MSG = "any error message text"; @@ -173,7 +174,7 @@ public class HoldServiceImplUnitTest extends BaseUnitTest } @Test (expected=AlfrescoRuntimeException.class) - public void getHold() + public void testGetHold() { // setup node service interactions when(mockedNodeService.getChildByName(eq(holdContainer), eq(ContentModel.ASSOC_CONTAINS), anyString())).thenReturn(null) @@ -194,19 +195,19 @@ public class HoldServiceImplUnitTest extends BaseUnitTest } @Test (expected=RuntimeException.class) - public void getHeldNotAHold() + public void testGetHeldNotAHold() { holdService.getHeld(recordFolder); } @Test - public void getHeldNoResults() + public void testGetHeldNoResults() { assertTrue(holdService.getHeld(hold).isEmpty()); } @Test - public void getHeldWithResults() + public void testGetHeldWithResults() { // setup record folder in hold List holds = new ArrayList<>(2); @@ -259,7 +260,7 @@ public class HoldServiceImplUnitTest extends BaseUnitTest } @Test - public void getHoldReason() + public void testGetHoldReason() { // setup node service interactions when(mockedNodeService.exists(hold)) @@ -306,6 +307,80 @@ public class HoldServiceImplUnitTest extends BaseUnitTest verify(mockedNodeService).setProperty(hold, PROP_HOLD_REASON, HOLD_REASON); } + @Test + public void setHoldDeletionReasonForNodeDoesNotExist() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(false); + + // node does not exist + holdService.setHoldDeletionReason(hold, HOLD_DELETION_REASON); + verify(mockedNodeService, never()).setProperty(hold, PROP_HOLD_DELETION_REASON, HOLD_DELETION_REASON); + } + + @Test + public void setHoldDeletionReasonForNodeIsNotAHold() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(true); + + // node isn't a hold + holdService.setHoldDeletionReason(recordFolder, HOLD_DELETION_REASON); + verify(mockedNodeService, never()).setProperty(hold, PROP_HOLD_DELETION_REASON, HOLD_DELETION_REASON); + } + + @Test + public void setHoldDeletionReason() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(true); + + // set hold deletion reason + holdService.setHoldDeletionReason(hold, HOLD_DELETION_REASON); + verify(mockedNodeService).setProperty(hold, PROP_HOLD_DELETION_REASON, HOLD_DELETION_REASON); + } + + @Test + public void updateHoldThatDoesNotExist() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(false); + + // node does not exist + holdService.updateHold(hold, HOLD_NAME, HOLD_REASON, HOLD_DESCRIPTION); + verify(mockedNodeService, never()).setProperty(any(NodeRef.class), any(QName.class), any(String.class)); + } + + @Test + public void updateHoldThatIsNotAHold() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(true); + + // node isn't a hold + holdService.updateHold(recordFolder, HOLD_NAME, HOLD_REASON, HOLD_DESCRIPTION); + verify(mockedNodeService, never()).setProperty(any(NodeRef.class), any(QName.class), any(String.class)); + } + + @Test + public void updateHold() + { + // setup node service interactions + when(mockedNodeService.exists(hold)) + .thenReturn(true); + + // update hold + holdService.updateHold(hold, HOLD_NAME, HOLD_REASON, HOLD_DESCRIPTION); + verify(mockedNodeService).setProperty(hold, ContentModel.PROP_NAME, HOLD_NAME); + verify(mockedNodeService).setProperty(hold, ContentModel.PROP_DESCRIPTION, HOLD_DESCRIPTION); + verify(mockedNodeService).setProperty(hold, PROP_HOLD_REASON, HOLD_REASON); + } + @Test (expected=AlfrescoRuntimeException.class) public void deleteHoldNotAHold() { diff --git a/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml b/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml index 2236659422..92bac98ac2 100644 --- a/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml +++ b/amps/ags/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions/gs-core-api.yaml @@ -38,6 +38,8 @@ tags: description: Retrieve and manage unfiled records containers - name: unfiled-record-folders description: Retrieve and manage unfiled record folders + - name: holds + description: Retrieve and manage holds paths: ## GS sites @@ -418,6 +420,124 @@ paths: description: New name clashes with an existing node in the current parent container '422': description: Model integrity exception, including node name with invalid characters + '/file-plans/{filePlanId}/holds': + get: + tags: + - file-plans + summary: Get all holds in a file plan + description: | + Returns a list of holds. + operationId: getHolds + parameters: + - $ref: '#/parameters/filePlanIdWithAliasParam' + - $ref: '#/parameters/skipCountParam' + - $ref: '#/parameters/maxItemsParam' + consumes: + - application/json + produces: + - application/json + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/HoldPaging' + '401': + description: Authentication failed + '403': + description: Current user does not have permission to read **filePlanId** + '404': + description: "**filePlanId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + post: + tags: + - file-plans + summary: Create holds for a file plan + description: | + Creates a new hold. + + You must specify at least a **name** and a **reason** property. + + **Note:** You can create more than one hold by specifying a list of holds in the JSON body. + For example, the following JSON body creates two holds: + ```JSON + [ + { + "name":"Hold1", + "description": "Description1", + "reason": "Reason1" + }, + { + "name":"Hold2", + "description": "Description2", + "reason": "Reason2" + } + ] + ``` + + If you specify a list as input, then a paginated list rather than an entry is returned in the response body. For example: + + ```JSON + { + "list": { + "pagination": { + "count": 2, + "hasMoreItems": false, + "totalItems": 2, + "skipCount": 0, + "maxItems": 100 + }, + "entries": [ + { + "entry": { + ... + } + }, + { + "entry": { + ... + } + } + ] + } + } + ``` + operationId: addHold + parameters: + - $ref: '#/parameters/filePlanIdWithAliasParam' + - in: body + name: nodeBodyCreate + description: The node information to create. + required: true + schema: + $ref: '#/definitions/HoldCreateBodyModel' + consumes: + - application/json + produces: + - application/json + responses: + '201': + description: Successful response + schema: + $ref: '#/definitions/HoldModelEntry' + '400': + description: | + Invalid parameter: **filePlanId** is not a valid format or **HoldCreateBodyModel** is not valid + '401': + description: Authentication failed + '403': + description: Current user does not have permission to create a hold + '404': + description: | + **filePlanId** does not exist + '409': + description: A hold with the name **name** already exists + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' ## Unfiled records containers '/unfiled-containers/{unfiledContainerId}': get: @@ -2092,6 +2212,289 @@ paths: description: Unexpected error schema: $ref: '#/definitions/Error' + ## Holds + '/holds/{holdId}': + get: + tags: + - holds + summary: Get a hold + description: | + Gets information for hold with id **holdId**. + operationId: getHold + parameters: + - $ref: '#/parameters/holdIdParam' + produces: + - application/json + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/HoldModelEntry' + '400': + description: | + Invalid parameter: **holdId** is not a valid format + '401': + description: Authentication failed + '403': + description: Current user does not have permission to read **holdId** + '404': + description: "**holdId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + put: + tags: + - holds + summary: Update a hold + description: | + Updates the hold with id **holdId**. For example, you can rename a hold: + ```JSON + { + "name":"My new name", + "description":"Existing description", + "reason":"Existing reason" + } + ``` + operationId: updateHold + parameters: + - $ref: '#/parameters/holdIdParam' + - in: body + name: holdBodyUpdate + description: The hold information to update. + required: true + schema: + $ref: '#/definitions/HoldCreateBodyModel' + produces: + - application/json + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/HoldModelEntry' + '400': + description: | + Invalid parameter: the update request is invalid or **holdId** is not a valid format or **holdBodyUpdate** is invalid + '401': + description: Authentication failed + '403': + description: Current user does not have permission to update **holdId** + '404': + description: "**holdId** does not exist" + '409': + description: Updated name clashes with an existing node in the current parent folder + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + delete: + tags: + - holds + summary: Delete a hold + description: | + Deletes the hold with id **holdId**. + operationId: deleteHold + parameters: + - $ref: '#/parameters/holdIdParam' + produces: + - application/json + responses: + '204': + description: Successful response + '400': + description: | + Invalid parameter: **holdId** is not a valid format + '401': + description: Authentication failed + '403': + description: Current user does not have permission to delete **holdId** + '404': + description: "**holdId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + '/holds/{holdId}/delete': + post: + tags: + - holds + summary: Delete a hold with a reason + description: | + Deletes the hold with id **holdId** and stores a reason for deletion in the audit log. + + A **reason** must be specified in the request body. + operationId: deleteHoldWithReason + parameters: + - $ref: '#/parameters/holdIdParam' + - in: body + name: holdDeletionReason + description: Reason for deletion. + required: true + schema: + $ref: '#/definitions/HoldDeletionReason' + produces: + - application/json + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/HoldDeletionReasonEntry' + '400': + description: | + Invalid parameter: **holdId** is not a valid format + '401': + description: Authentication failed + '403': + description: Current user does not have permission to update or delete **holdId** + '404': + description: "**holdId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + '/holds/{holdId}/children': + post: + tags: + - holds + summary: Add children to a hold + description: | + Add a child of a hold with id **holdId**. + + You must specify the child **id**. + + The API returns a 201 Created if the child is already a child of the hold. + + **Note:** You can add more than one child by specifying a list of children in the JSON body. + For example, the following JSON body adds two children: + ```JSON + [ + { + "id":"a7c10f46-b85b-4de5-af1c-930056b736a7" + }, + { + "id":"e0d79b71-be2b-4ce7-a846-a7c50cba20fb" + } + ] + ``` + + If you specify a list as input, then a paginated list rather than an entry is returned in the response body. For example: + + ```JSON + { + "list": { + "pagination": { + "count": 2, + "hasMoreItems": false, + "totalItems": 2, + "skipCount": 0, + "maxItems": 100 + }, + "entries": [ + { + "entry": { + ... + } + }, + { + "entry": { + ... + } + } + ] + } + } + ``` + operationId: addChildToHold + parameters: + - $ref: '#/parameters/holdIdParam' + - in: body + name: nodeId + description: The node id. + required: true + schema: + $ref: '#/definitions/HoldChild' + consumes: + - application/json + produces: + - application/json + responses: + '201': + description: Successful response + schema: + $ref: '#/definitions/HoldChildEntry' + '400': + description: | + Invalid parameter: **holdId** is not a valid format or **HoldChild** is not valid + '401': + description: Authentication failed + '403': + description: Current user does not have permission to add items to the hold + '404': + description: | + **holdId** does not exist + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + get: + tags: + - holds + summary: Get children of a hold + description: | + Returns a list of children of a hold with id **holdId**. + operationId: getHoldChildren + parameters: + - $ref: '#/parameters/holdIdParam' + - $ref: '#/parameters/skipCountParam' + - $ref: '#/parameters/maxItemsParam' + consumes: + - application/json + produces: + - application/json + responses: + '200': + description: Successful response + schema: + $ref: '#/definitions/HoldChildPaging' + '401': + description: Authentication failed + '403': + description: Current user does not have permission to read **holdId** + '404': + description: "**holdId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + '/holds/{holdId}/children/{holdChildId}': + delete: + tags: + - holds + summary: Delete a child of a hold + description: | + Deletes the relationship between a child with id **holdChildId** and a parent hold with id **holdId**. + operationId: removeHoldChild + parameters: + - $ref: '#/parameters/holdIdParam' + - $ref: '#/parameters/holdChildIdParam' + produces: + - application/json + responses: + '204': + description: Successful response + '400': + description: | + Invalid parameter: **holdChildId** or **holdId** is not a valid format + '401': + description: Authentication failed + '403': + description: Current user does not have permission to delete **holdChildId** + '404': + description: " **holdChildId** or **holdId** does not exist" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' parameters: ## File plans @@ -2175,7 +2578,7 @@ parameters: description: Also include **source** (in addition to **entries**) with folder information on the parent node – the specified parent **unfiledContainerId** required: false type: boolean -## Unfiled record folders + ## Unfiled record folders unfiledRecordFolderIdParam: name: unfiledRecordFolderId in: path @@ -2446,6 +2849,19 @@ parameters: items: type: string collectionFormat: csv + # Holds + holdIdParam: + name: holdId + in: path + description: The identifier of a hold. + required: true + type: string + holdChildIdParam: + name: holdChildId + in: path + description: The identifier of a child of a hold. + required: true + type: string ## Record recordIdParam: name: recordId @@ -3519,6 +3935,89 @@ definitions: properties: association: $ref: '#/definitions/ChildAssociationInfo' + ## Holds + HoldModelEntry: + type: object + required: + - entry + properties: + entry: + $ref: '#/definitions/HoldModel' + HoldModel: + type: object + properties: + id: + type: string + name: + type: string + description: + type: string + reason: + type: string + HoldCreateBodyModel: + type: object + required: + - name + - reason + properties: + name: + type: string + description: + type: string + reason: + type: string + HoldPaging: + type: object + properties: + list: + type: object + properties: + pagination: + $ref: '#/definitions/Pagination' + entries: + type: array + items: + $ref: '#/definitions/HoldModelEntry' + HoldChild: + type: object + required: + - id + properties: + id: + type: string + HoldChildEntry: + type: object + required: + - entry + properties: + entry: + $ref: '#/definitions/HoldChild' + HoldChildPaging: + type: object + properties: + list: + type: object + properties: + pagination: + $ref: '#/definitions/Pagination' + entries: + type: array + items: + $ref: '#/definitions/HoldChildEntry' + HoldDeletionReasonEntry: + type: object + required: + - entry + properties: + entry: + $ref: '#/definitions/HoldDeletionReason' + HoldDeletionReason: + type: object + required: + - reason + properties: + reason: + type: string ## RequestBodyFile: type: object