mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
ACS-7556 Bulk update in Legal Holds (#2692)
* ACS-7557 Add Legal Holds Bulk v1 API (#2624) * ACS-7557 Add Legal Holds Bulk v1 API * ACS-7557 Improve v1 API * ACS-7557 Replace processId with bulkStatusId * ACS-7587 Implement v1 Bulk API to add items to a hold (#2656) * ACS-7557 Add bulk API design * ACS-7557 Fix * ACS-7557 Add permissions checks * ACS-7557 Add IT tests * ACS-7557 Add comments + logging * ACS-7557 Refactor * ACS-7557 Reimplement task container * ACS-7557 Refactor code * ACS-7587 Remove merge leftovers * ACS-7587 Refactor * ACS-7587 Tests * ACS-7587 Change DefaultHoldBulkMonitor * ACS-7587 Reimplement BulkStatusUpdater * ACS-7587 Fix PMD issues * ACS-7587 Fix PMD isues * ACS-7587 Refactor * ACS-7587 Add test files alternately * ACS-7587 Refactor code * ACS-7587 Improve search query * ACS-7587 Fix PMD issues * ACS-7587 Fix PMD issue * ACS-7587 Fix intermittent failure * ACS7587 Fix intermittent failure (#2681) * ACS-7587 Implement bulk cancellations (#2683)
This commit is contained in:
@@ -84,6 +84,12 @@
|
||||
<artifactId>okhttp</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${dependency.awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
|
@@ -0,0 +1,41 @@
|
||||
/*-
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.rm.community.model.hold;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BulkBodyCancel
|
||||
{
|
||||
private String reason;
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #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.NoArgsConstructor;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.utility.model.TestModel;
|
||||
|
||||
/**
|
||||
* POJO for hold bulk request
|
||||
*
|
||||
* @author Damian Ujma
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HoldBulkOperation extends TestModel
|
||||
{
|
||||
public enum HoldBulkOperationType
|
||||
{
|
||||
ADD
|
||||
}
|
||||
|
||||
@JsonProperty(required = true)
|
||||
private RestRequestQueryModel query;
|
||||
@JsonProperty(required = true)
|
||||
private HoldBulkOperationType op;
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #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.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* POJO for hold bulk request entry
|
||||
*
|
||||
* @author Damian Ujma
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HoldBulkOperationEntry
|
||||
{
|
||||
private String bulkStatusId;
|
||||
|
||||
private long totalItems;
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.rm.community.model.hold;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.alfresco.utility.model.TestModel;
|
||||
|
||||
/**
|
||||
* POJO for hold bulk request
|
||||
*
|
||||
* @author Damian Ujma
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HoldBulkStatus extends TestModel
|
||||
{
|
||||
private String bulkStatusId;
|
||||
|
||||
private String startTime;
|
||||
|
||||
private String endTime;
|
||||
|
||||
private long processedItems;
|
||||
|
||||
private long errorsCount;
|
||||
|
||||
private long totalItems;
|
||||
|
||||
private String lastError;
|
||||
|
||||
private String status;
|
||||
|
||||
private boolean isCancelled;
|
||||
|
||||
private String cancellationReason;
|
||||
|
||||
private HoldBulkOperation holdBulkOperation;
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.rm.community.model.hold;
|
||||
|
||||
import org.alfresco.rest.core.RestModels;
|
||||
|
||||
/**
|
||||
* Handle collection of {@link HoldBulkStatusEntry}
|
||||
*
|
||||
* @author Damian Ujma
|
||||
*/
|
||||
public class HoldBulkStatusCollection extends RestModels<HoldBulkStatusEntry, HoldBulkStatusCollection>
|
||||
{
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #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;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HoldBulkStatusEntry extends RestModels<HoldBulkStatus, HoldBulkStatusEntry>
|
||||
{
|
||||
private HoldBulkStatus entry;
|
||||
}
|
@@ -48,5 +48,5 @@ import org.alfresco.rest.core.RestModels;
|
||||
public class HoldChildEntry extends RestModels<Hold, HoldChildEntry>
|
||||
{
|
||||
@JsonProperty
|
||||
private HoldChildEntry entry;
|
||||
private HoldChild entry;
|
||||
}
|
@@ -38,7 +38,12 @@ 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.BulkBodyCancel;
|
||||
import org.alfresco.rest.rm.community.model.hold.Hold;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
|
||||
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;
|
||||
@@ -287,4 +292,155 @@ public class HoldsAPI extends RMModelRequest
|
||||
{
|
||||
deleteHoldChild(holdId, holdChildId, EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a bulk process for a hold.
|
||||
*
|
||||
* @param holdBulkOperation The bulk operation details
|
||||
* @param hold The identifier of a hold
|
||||
* @param parameters The URL parameters to add
|
||||
* @return The {@link HoldBulkOperationEntry} for the started bulk process
|
||||
* @throws RuntimeException for the following cases:
|
||||
* <ul>
|
||||
* <li>{@code hold} or {@code holdBulkOperation} is invalid</li>
|
||||
* <li>authentication fails</li>
|
||||
* <li>current user does not have permission to start a bulk process for {@code hold}</li>
|
||||
* <li>{@code hold} does not exist</li>
|
||||
* </ul>
|
||||
*/
|
||||
public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold, String parameters)
|
||||
{
|
||||
mandatoryObject("holdBulkOperation", holdBulkOperation);
|
||||
mandatoryString("hold", hold);
|
||||
|
||||
return getRmRestWrapper().processModel(HoldBulkOperationEntry.class, requestWithBody(
|
||||
POST,
|
||||
toJson(holdBulkOperation),
|
||||
"holds/{hold}/bulk",
|
||||
hold,
|
||||
parameters
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #startBulkProcess(HoldBulkOperation, String, String)}
|
||||
*/
|
||||
public HoldBulkOperationEntry startBulkProcess(HoldBulkOperation holdBulkOperation, String hold)
|
||||
{
|
||||
return startBulkProcess(holdBulkOperation, hold, EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of a bulk process for a hold.
|
||||
*
|
||||
* @param holdId The identifier of a hold
|
||||
* @param holdBulkStatusId The identifier of a bulk status operation
|
||||
* @param parameters The URL parameters to add
|
||||
* @return The {@link HoldBulkStatus} for the given {@code holdId} and {@code holdBulkStatusId}
|
||||
* @throws RuntimeException for the following cases:
|
||||
* <ul>
|
||||
* <li>{@code holdId} or {@code holdBulkStatusId} is invalid</li>
|
||||
* <li>authentication fails</li>
|
||||
* <li>current user does not have permission to get the bulk status for {@code holdId}</li>
|
||||
* <li>{@code holdId} or {@code holdBulkStatusId} does not exist</li>
|
||||
* </ul>
|
||||
*/
|
||||
public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId, String parameters)
|
||||
{
|
||||
mandatoryString("holdId", holdId);
|
||||
mandatoryString("holdBulkStatusId", holdBulkStatusId);
|
||||
|
||||
return getRmRestWrapper().processModel(HoldBulkStatus.class, simpleRequest(
|
||||
GET,
|
||||
"holds/{holdId}/bulk-statuses/{holdBulkStatusId}",
|
||||
holdId,
|
||||
holdBulkStatusId,
|
||||
parameters
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #getBulkStatus(String, String, String)}
|
||||
*/
|
||||
public HoldBulkStatus getBulkStatus(String holdId, String holdBulkStatusId)
|
||||
{
|
||||
return getBulkStatus(holdId, holdBulkStatusId, EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the statuses of all bulk processes for a hold.
|
||||
*
|
||||
* @param holdId The identifier of a hold
|
||||
* @param parameters The URL parameters to add
|
||||
* @return The {@link HoldBulkStatusCollection} for the given {@code holdId}
|
||||
* @throws RuntimeException for the following cases:
|
||||
* <ul>
|
||||
* <li>{@code holdId} is invalid</li>
|
||||
* <li>authentication fails</li>
|
||||
* <li>current user does not have permission to get the bulk statuses for {@code holdId}</li>
|
||||
* <li>{@code holdId} does not exist</li>
|
||||
* </ul>
|
||||
*/
|
||||
public HoldBulkStatusCollection getBulkStatuses(String holdId, String parameters)
|
||||
{
|
||||
mandatoryString("holdId", holdId);
|
||||
|
||||
return getRmRestWrapper().processModels(HoldBulkStatusCollection.class, simpleRequest(
|
||||
GET,
|
||||
"holds/{holdId}/bulk-statuses",
|
||||
holdId,
|
||||
parameters
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #getBulkStatuses(String, String)}
|
||||
*/
|
||||
public HoldBulkStatusCollection getBulkStatuses(String holdId)
|
||||
{
|
||||
return getBulkStatuses(holdId, EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a bulk operation for a hold.
|
||||
*
|
||||
* @param holdId The identifier of a hold
|
||||
* @param bulkStatusId The identifier of a bulk status operation
|
||||
* @param bulkBodyCancel The bulk body cancel model
|
||||
* @param parameters The URL parameters to add
|
||||
* @throws RuntimeException for the following cases:
|
||||
* <ul>
|
||||
* <li>{@code holdId}, {@code bulkStatusId} or {@code bulkBodyCancel} is invalid</li>
|
||||
* <li>authentication fails</li>
|
||||
* <li>current user does not have permission to cancel the bulk operation for {@code bulkStatusId}</li>
|
||||
* <li>{@code holdId} or {@code bulkStatusId} does not exist</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void cancelBulkOperation(String holdId, String bulkStatusId, BulkBodyCancel bulkBodyCancel, String parameters)
|
||||
{
|
||||
mandatoryString("holdId", holdId);
|
||||
mandatoryString("bulkStatusId", bulkStatusId);
|
||||
mandatoryObject("bulkBodyCancel", bulkBodyCancel);
|
||||
|
||||
getRmRestWrapper().processEmptyModel(requestWithBody(
|
||||
POST,
|
||||
toJson(bulkBodyCancel),
|
||||
"holds/{holdId}/bulk-statuses/{bulkStatusId}/cancel",
|
||||
holdId,
|
||||
bulkStatusId,
|
||||
parameters
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #cancelBulkOperation(String, String, BulkBodyCancel, String)}
|
||||
*/
|
||||
public void cancelBulkOperation(String holdId, String bulkStatusId, BulkBodyCancel bulkBodyCancel)
|
||||
{
|
||||
mandatoryString("holdId", holdId);
|
||||
mandatoryString("bulkStatusId", bulkStatusId);
|
||||
mandatoryObject("bulkBodyCancel", bulkBodyCancel);
|
||||
|
||||
cancelBulkOperation(holdId, bulkStatusId, bulkBodyCancel, EMPTY);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,614 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #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.user.UserPermissions.PERMISSION_FILING;
|
||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
|
||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.http.HttpStatus.ACCEPTED;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.dataprep.ContentActions;
|
||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
|
||||
import org.alfresco.rest.rm.community.model.hold.BulkBodyCancel;
|
||||
import org.alfresco.rest.rm.community.model.hold.Hold;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperation.HoldBulkOperationType;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkOperationEntry;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatus;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusCollection;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldBulkStatusEntry;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldChild;
|
||||
import org.alfresco.rest.rm.community.model.hold.HoldChildEntry;
|
||||
import org.alfresco.rest.rm.community.model.user.UserRoles;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.rest.v0.service.RoleService;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
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;
|
||||
|
||||
/**
|
||||
* API tests for adding items to holds via the bulk process
|
||||
*/
|
||||
public class AddToHoldsBulkV1Tests 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 int NUMBER_OF_FILES = 5;
|
||||
private final List<FileModel> addedFiles = new ArrayList<>();
|
||||
private final List<UserModel> users = new ArrayList<>();
|
||||
private final List<Hold> holds = new ArrayList<>();
|
||||
private Hold hold;
|
||||
private Hold hold2;
|
||||
private Hold hold3;
|
||||
private FolderModel rootFolder;
|
||||
private HoldBulkOperation holdBulkOperation;
|
||||
@Autowired
|
||||
private RoleService roleService;
|
||||
@Autowired
|
||||
private ContentActions contentActions;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void preconditionForAddContentToHold()
|
||||
{
|
||||
STEP("Create a hold.");
|
||||
hold = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold);
|
||||
|
||||
STEP("Create test files.");
|
||||
testSite = dataSite.usingAdmin().createPublicRandomSite();
|
||||
|
||||
rootFolder = dataContent.usingAdmin().usingSite(testSite).createFolder();
|
||||
FolderModel folder1 = dataContent.usingAdmin().usingResource(rootFolder).createFolder();
|
||||
FolderModel folder2 = dataContent.usingAdmin().usingResource(folder1).createFolder();
|
||||
|
||||
// Add files to subfolders in the site
|
||||
for (int i = 0; i < NUMBER_OF_FILES; i++)
|
||||
{
|
||||
FileModel documentHeld = dataContent.usingAdmin()
|
||||
.usingResource(i % 2 == 0 ? folder1 : folder2)
|
||||
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
addedFiles.add(documentHeld);
|
||||
}
|
||||
|
||||
RestRequestQueryModel queryReq = getContentFromSiteQuery(testSite.getId());
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
searchRequest.setQuery(queryReq);
|
||||
|
||||
STEP("Wait until all files are searchable.");
|
||||
await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
holdBulkOperation = HoldBulkOperation.builder()
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
*/
|
||||
@Test
|
||||
public void addContentFromTestSiteToHoldUsingBulkAPI()
|
||||
{
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(20, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, holdBulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold.getId());
|
||||
assertEquals(Arrays.asList(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a folder and all subfolders to a hold using the bulk API
|
||||
* Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
*/
|
||||
@Test
|
||||
public void addContentFromFolderAndAllSubfoldersToHoldUsingBulkAPI()
|
||||
{
|
||||
hold3 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold3);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold3.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
// Get content from folder and all subfolders of the root folder
|
||||
HoldBulkOperation bulkOperation = HoldBulkOperation.builder()
|
||||
.query(getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion()))
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(bulkOperation, hold3.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(20, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold3.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold3.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold3.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, bulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold3.getId());
|
||||
assertEquals(List.of(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the add to hold capability
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutAddToHoldCapability()
|
||||
{
|
||||
UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole
|
||||
.SiteCollaborator,
|
||||
hold.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
users.add(userWithoutAddToHoldCapability);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the filing permission on a hold
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutFilingPermissionOnAHold()
|
||||
{
|
||||
// User without filing permission on a hold
|
||||
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS);
|
||||
users.add(userWithoutPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the write permission on all the content
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then all processed items are marked as errors and the last error message contains access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutWritePermissionOnTheContent()
|
||||
{
|
||||
// User without write permission on the content
|
||||
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(
|
||||
testSite, UserRole.SiteConsumer,
|
||||
hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userWithoutPermission);
|
||||
|
||||
// Wait until permissions are reverted
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
searchRequest.setQuery(holdBulkOperation.getQuery());
|
||||
await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(userWithoutPermission).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(
|
||||
userWithoutPermission).startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response.");
|
||||
assertStatusCode(ACCEPTED);
|
||||
|
||||
await().atMost(20, TimeUnit.SECONDS).until(() ->
|
||||
Objects.equals(getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId()).getStatus(), "DONE"));
|
||||
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, NUMBER_OF_FILES, ACCESS_DENIED_ERROR_MESSAGE,
|
||||
holdBulkOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the write permission on one file
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then all processed items are added to the hold except the one that the user does not have write permission
|
||||
* And the status of the bulk operation is DONE, contains the error message and the number of errors is 1
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutWritePermissionOnOneFile()
|
||||
{
|
||||
hold2 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold2);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold2.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteConsumer.getRoleId(), false);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold2.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(30, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold2.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES - 1);
|
||||
await().atMost(30, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId()).getProcessedItems()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold2.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().skip(1).map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 1, ACCESS_DENIED_ERROR_MESSAGE, holdBulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold2.getId());
|
||||
assertEquals(List.of(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
|
||||
// Revert the permissions
|
||||
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteCollaborator.getRoleId(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an unauthenticated user
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives unauthorized error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessAsUnauthenticatedUser()
|
||||
{
|
||||
STEP("Start bulk process as unauthenticated user");
|
||||
getRestAPIFactory().getHoldsAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword"))
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessForNonExistentHold()
|
||||
{
|
||||
STEP("Start bulk process for non existent hold");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(holdBulkOperation, "nonExistentHoldId");
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* and the bulk operation is invalid
|
||||
* Then the user receives bad request error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusesForInvalidOperation()
|
||||
{
|
||||
STEP("Start bulk process for non existent hold");
|
||||
|
||||
HoldBulkOperation invalidHoldBulkOperation = HoldBulkOperation.builder().op(null)
|
||||
.query(holdBulkOperation.getQuery()).build();
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(invalidHoldBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusForNonExistentHold()
|
||||
{
|
||||
STEP("Start bulk process for non existent hold");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus("nonExistentHoldId", "nonExistenBulkStatusId");
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the bulk status does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusForNonExistentBulkStatus()
|
||||
{
|
||||
STEP("Start bulk process for non bulk status");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatus(hold.getId(), "nonExistenBulkStatusId");
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusesForNonExistentHold()
|
||||
{
|
||||
STEP("Start bulk process for non existent hold");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).getBulkStatuses("nonExistentHoldId");
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from all sites to a hold using the bulk API to exceed the limit (30 items)
|
||||
* Then the user receives bad request error
|
||||
*/
|
||||
@Test
|
||||
public void testExceedingBulkOperationLimit()
|
||||
{
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery("TYPE:content");
|
||||
queryReq.setLanguage("afts");
|
||||
|
||||
HoldBulkOperation exceedLimitOp = HoldBulkOperation.builder()
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
|
||||
STEP("Start bulk process to exceed the limit");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(exceedLimitOp, hold.getId());
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And then the user cancels the bulk operation
|
||||
* Then the user receives OK status code
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessCancellationWithAllowedUser()
|
||||
{
|
||||
Hold hold4 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold4);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold4.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold4.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
STEP("Cancel the bulk operation.");
|
||||
getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.cancelBulkOperation(hold4.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And a 2nd user without the add to hold capability cancels the bulk operation
|
||||
* Then the 2nd user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessCancellationWithUserWithoutAddToHoldCapability()
|
||||
{
|
||||
Hold hold5 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold5);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold5.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold5.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole
|
||||
.SiteCollaborator,
|
||||
hold5.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
users.add(userWithoutAddToHoldCapability);
|
||||
|
||||
STEP("Cancel the bulk operation.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
|
||||
.cancelBulkOperation(hold5.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
getRestAPIFactory().getRmRestWrapper().assertLastError().containsSummary(ACCESS_DENIED_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
private void assertBulkProcessStatus(HoldBulkStatus holdBulkStatus, long expectedProcessedItems,
|
||||
int expectedErrorsCount, String expectedErrorMessage, HoldBulkOperation holdBulkOperation)
|
||||
{
|
||||
assertEquals("DONE", holdBulkStatus.getStatus());
|
||||
assertEquals(expectedProcessedItems, holdBulkStatus.getTotalItems());
|
||||
assertEquals(expectedProcessedItems, holdBulkStatus.getProcessedItems());
|
||||
assertEquals(expectedErrorsCount, holdBulkStatus.getErrorsCount());
|
||||
assertEquals(holdBulkStatus.getHoldBulkOperation(), holdBulkOperation);
|
||||
assertNotNull(holdBulkStatus.getStartTime());
|
||||
assertNotNull(holdBulkStatus.getEndTime());
|
||||
|
||||
if (expectedErrorMessage != null)
|
||||
{
|
||||
assertTrue(holdBulkStatus.getLastError().contains(expectedErrorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private RestRequestQueryModel getContentFromSiteQuery(String siteId)
|
||||
{
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery("SITE:\"" + siteId + "\" and TYPE:content");
|
||||
queryReq.setLanguage("afts");
|
||||
return queryReq;
|
||||
}
|
||||
|
||||
private RestRequestQueryModel getContentFromFolderAndAllSubfoldersQuery(String folderId)
|
||||
{
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery("ANCESTOR:\"workspace://SpacesStore/" + folderId + "\" and TYPE:content");
|
||||
queryReq.setLanguage("afts");
|
||||
return queryReq;
|
||||
}
|
||||
|
||||
@AfterClass(alwaysRun = true)
|
||||
public void cleanupAddToHoldsBulkV1Tests()
|
||||
{
|
||||
dataSite.usingAdmin().deleteSite(testSite);
|
||||
users.forEach(user -> getDataUser().usingAdmin().deleteUser(user));
|
||||
holds.forEach(hold -> getRestAPIFactory().getHoldsAPI(getAdminUser()).deleteHold(hold.getId()));
|
||||
}
|
||||
}
|
@@ -139,3 +139,26 @@ content.metadata.async.extract.6.enabled=false
|
||||
|
||||
# Max number of entries returned in Record search view
|
||||
rm.recordSearch.maxItems=500
|
||||
|
||||
#
|
||||
# Hold bulk
|
||||
#
|
||||
# The number of worker threads.
|
||||
rm.hold.bulk.threadCount=2
|
||||
# The maximum number of total items to process in a single bulk operation.
|
||||
rm.hold.bulk.maxItems=1000
|
||||
# The number of entries to be fetched from the Search Service as a next set of work object to process.
|
||||
rm.hold.bulk.batchSize=100
|
||||
# The number of entries to process before reporting progress.
|
||||
rm.hold.bulk.logging.interval=100
|
||||
# The number of entries we process at a time in a transaction.
|
||||
rm.hold.bulk.itemsPerTransaction=1
|
||||
# The maximum number of bulk requests we can process in parallel.
|
||||
rm.hold.bulk.maxParallelRequests=10
|
||||
|
||||
cache.bulkHoldStatusCache.cluster.type=fully-distributed
|
||||
cache.bulkHoldStatusCache.timeToLiveSeconds=2592000
|
||||
cache.bulkHoldRegistryCache.cluster.type=fully-distributed
|
||||
cache.bulkHoldRegistryCache.timeToLiveSeconds=2592000
|
||||
cache.bulkCancellationsCache.cluster.type=fully-distributed
|
||||
cache.bulkCancellationsCache.timeToLiveSeconds=2592000
|
@@ -89,6 +89,9 @@
|
||||
<!-- Import RM Audit -->
|
||||
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-audit-context.xml"/>
|
||||
|
||||
<!-- Import RM Bulk -->
|
||||
<import resource="classpath:alfresco/module/org_alfresco_module_rm/rm-bulk-context.xml"/>
|
||||
|
||||
<!-- Import RM query context -->
|
||||
<import resource="classpath:alfresco/module/org_alfresco_module_rm/query/rm-query-context.xml" />
|
||||
|
||||
|
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="holdBulkService"
|
||||
class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkServiceImpl">
|
||||
<property name="serviceRegistry" ref="ServiceRegistry" />
|
||||
<property name="transactionService" ref="transactionService" />
|
||||
<property name="searchMapper" ref="searchapiSearchMapper" />
|
||||
<property name="bulkMonitor" ref="holdBulkMonitor" />
|
||||
<property name="holdService" ref="HoldService" />
|
||||
<property name="capabilityService" ref="CapabilityService" />
|
||||
<property name="permissionService" ref="PermissionService" />
|
||||
<property name="nodeService" ref="NodeService" />
|
||||
<property name="threadCount">
|
||||
<value>${rm.hold.bulk.threadCount}</value>
|
||||
</property>
|
||||
<property name="batchSize">
|
||||
<value>${rm.hold.bulk.batchSize}</value>
|
||||
</property>
|
||||
<property name="maxItems">
|
||||
<value>${rm.hold.bulk.maxItems}</value>
|
||||
</property>
|
||||
<property name="loggingInterval">
|
||||
<value>${rm.hold.bulk.logging.interval}</value>
|
||||
</property>
|
||||
<property name="itemsPerTransaction">
|
||||
<value>${rm.hold.bulk.itemsPerTransaction}</value>
|
||||
</property>
|
||||
<property name="maxParallelRequests">
|
||||
<value>${rm.hold.bulk.maxParallelRequests}</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="holdBulkMonitor" class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor">
|
||||
<property name="holdProgressCache" ref="holdProgressCache" />
|
||||
<property name="holdProcessRegistry" ref="holdProcessRegistry" />
|
||||
<property name="bulkCancellationsCache" ref="bulkCancellationsCache" />
|
||||
</bean>
|
||||
|
||||
|
||||
<bean name="holdProgressCache" factory-bean="cacheFactory" factory-method="createCache">
|
||||
<constructor-arg value="cache.bulkHoldStatusCache" />
|
||||
</bean>
|
||||
|
||||
<bean name="holdProcessRegistry" factory-bean="cacheFactory" factory-method="createCache">
|
||||
<constructor-arg value="cache.bulkHoldRegistryCache" />
|
||||
</bean>
|
||||
|
||||
<bean name="bulkCancellationsCache" factory-bean="cacheFactory" factory-method="createCache">
|
||||
<constructor-arg value="cache.bulkCancellationsCache" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
@@ -83,6 +83,14 @@
|
||||
<property name="nodesModelFactory" ref="nodesModelFactory" />
|
||||
<property name="fileFolderService" ref="FileFolderService" />
|
||||
<property name="transactionService" ref="transactionService" />
|
||||
<property name="holdBulkService" ref="holdBulkService" />
|
||||
</bean>
|
||||
|
||||
<bean class="org.alfresco.rm.rest.api.holds.HoldsBulkStatusesRelation" >
|
||||
<property name="holdBulkMonitor" ref="holdBulkMonitor" />
|
||||
<property name="holdBulkService" ref="holdBulkService" />
|
||||
<property name="apiUtils" ref="apiUtils" />
|
||||
<property name="permissionService" ref="PermissionService" />
|
||||
</bean>
|
||||
|
||||
<bean class="org.alfresco.rm.rest.api.holds.HoldsChildrenRelation">
|
||||
|
@@ -41,6 +41,8 @@ services:
|
||||
-Daos.baseUrlOverwrite=http://localhost:8080/alfresco/aos
|
||||
-Dmessaging.broker.url=\"failover:(tcp://activemq:61616)?timeout=3000&jms.useCompression=true\"
|
||||
-DlocalTransform.core-aio.url=http://transform-core-aio:8090/
|
||||
-Drm.hold.bulk.maxItems=5
|
||||
-Drm.hold.bulk.batchSize=2
|
||||
"
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
@@ -155,6 +155,12 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${dependency.awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import static java.util.concurrent.Executors.newFixedThreadPool;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||
import org.alfresco.repo.batch.BatchProcessor;
|
||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
|
||||
import org.alfresco.rest.api.search.impl.SearchMapper;
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* A base class for executing bulk operations on nodes based on search query results
|
||||
*/
|
||||
public abstract class BulkBaseService<T> implements InitializingBean
|
||||
{
|
||||
private static final Log LOG = LogFactory.getLog(BulkBaseService.class);
|
||||
protected ExecutorService executorService;
|
||||
protected ServiceRegistry serviceRegistry;
|
||||
protected SearchService searchService;
|
||||
protected TransactionService transactionService;
|
||||
protected SearchMapper searchMapper;
|
||||
protected BulkMonitor<T> bulkMonitor;
|
||||
|
||||
protected int threadCount;
|
||||
protected int batchSize;
|
||||
protected int itemsPerTransaction;
|
||||
protected int maxItems;
|
||||
protected int loggingInterval;
|
||||
protected int maxParallelRequests;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
this.searchService = serviceRegistry.getSearchService();
|
||||
this.executorService = newFixedThreadPool(maxParallelRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute bulk operation on node based on the search query results
|
||||
*
|
||||
* @param nodeRef node reference
|
||||
* @param bulkOperation bulk operation
|
||||
* @return bulk status
|
||||
*/
|
||||
public T execute(NodeRef nodeRef, BulkOperation bulkOperation)
|
||||
{
|
||||
checkPermissions(nodeRef, bulkOperation);
|
||||
|
||||
ResultSet resultSet = getTotalItems(bulkOperation.searchQuery(), maxItems);
|
||||
if (maxItems < resultSet.getNumberFound() || resultSet.hasMore())
|
||||
{
|
||||
throw new InvalidArgumentException("Too many items to process. Please refine your query.");
|
||||
}
|
||||
long totalItems = resultSet.getNumberFound();
|
||||
// Generate a random process id
|
||||
String processId = UUID.randomUUID().toString();
|
||||
|
||||
T initBulkStatus = getInitBulkStatus(processId, totalItems);
|
||||
bulkMonitor.updateBulkStatus(initBulkStatus);
|
||||
bulkMonitor.registerProcess(nodeRef, processId, bulkOperation);
|
||||
|
||||
BulkProgress bulkProgress = new BulkProgress(totalItems, processId, new AtomicBoolean(false),
|
||||
new AtomicInteger(0));
|
||||
BatchProcessWorker<NodeRef> batchProcessWorker = getWorkerProvider(nodeRef, bulkOperation, bulkProgress);
|
||||
BulkStatusUpdater bulkStatusUpdater = getBulkStatusUpdater();
|
||||
|
||||
BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<>(
|
||||
processId,
|
||||
transactionService.getRetryingTransactionHelper(),
|
||||
getWorkProvider(bulkOperation, bulkStatusUpdater, bulkProgress),
|
||||
threadCount,
|
||||
itemsPerTransaction,
|
||||
bulkStatusUpdater,
|
||||
LOG,
|
||||
loggingInterval);
|
||||
|
||||
runAsyncBatchProcessor(batchProcessor, batchProcessWorker, bulkStatusUpdater);
|
||||
return initBulkStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run batch processor
|
||||
*/
|
||||
protected void runAsyncBatchProcessor(BatchProcessor<NodeRef> batchProcessor,
|
||||
BatchProcessWorker<NodeRef> batchProcessWorker, BulkStatusUpdater bulkStatusUpdater)
|
||||
{
|
||||
Runnable backgroundLogic = () -> {
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Started processing batch with name: " + batchProcessor.getProcessName());
|
||||
}
|
||||
batchProcessor.processLong(batchProcessWorker, true);
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Processing batch with name: " + batchProcessor.getProcessName() + " completed");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
LOG.error("Error processing batch with name: " + batchProcessor.getProcessName(), exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bulkStatusUpdater.update();
|
||||
}
|
||||
};
|
||||
|
||||
executorService.submit(backgroundLogic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initial bulk status
|
||||
*
|
||||
* @param processId process id
|
||||
* @param totalItems total items
|
||||
* @return bulk status
|
||||
*/
|
||||
protected abstract T getInitBulkStatus(String processId, long totalItems);
|
||||
|
||||
/**
|
||||
* Get bulk status updater
|
||||
*
|
||||
* @return bulk status updater
|
||||
*/
|
||||
protected abstract BulkStatusUpdater getBulkStatusUpdater();
|
||||
|
||||
/**
|
||||
* Get work provider
|
||||
*
|
||||
* @param bulkOperation bulk operation
|
||||
* @param bulkStatusUpdater bulk status updater
|
||||
* @param bulkProgress bulk progress
|
||||
* @return work provider
|
||||
*/
|
||||
protected abstract BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation,
|
||||
BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress);
|
||||
|
||||
/**
|
||||
* Get worker provider
|
||||
*
|
||||
* @param nodeRef node reference
|
||||
* @param bulkOperation bulk operation
|
||||
* @param bulkProgress bulk progress
|
||||
* @return worker provider
|
||||
*/
|
||||
protected abstract BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation,
|
||||
BulkProgress bulkProgress);
|
||||
|
||||
/**
|
||||
* Check permissions
|
||||
*
|
||||
* @param nodeRef node reference
|
||||
* @param bulkOperation bulk operation
|
||||
*/
|
||||
protected abstract void checkPermissions(NodeRef nodeRef, BulkOperation bulkOperation);
|
||||
|
||||
protected ResultSet getTotalItems(Query searchQuery, int skipCount)
|
||||
{
|
||||
SearchParameters searchParams = new SearchParameters();
|
||||
searchMapper.setDefaults(searchParams);
|
||||
searchMapper.fromQuery(searchParams, searchQuery);
|
||||
searchParams.setSkipCount(skipCount);
|
||||
searchParams.setMaxItems(1);
|
||||
searchParams.setLimit(1);
|
||||
return searchService.query(searchParams);
|
||||
}
|
||||
|
||||
public void setServiceRegistry(ServiceRegistry serviceRegistry)
|
||||
{
|
||||
this.serviceRegistry = serviceRegistry;
|
||||
}
|
||||
|
||||
public void setSearchService(SearchService searchService)
|
||||
{
|
||||
this.searchService = searchService;
|
||||
}
|
||||
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
public void setSearchMapper(SearchMapper searchMapper)
|
||||
{
|
||||
this.searchMapper = searchMapper;
|
||||
}
|
||||
|
||||
public void setBulkMonitor(BulkMonitor<T> bulkMonitor)
|
||||
{
|
||||
this.bulkMonitor = bulkMonitor;
|
||||
}
|
||||
|
||||
public void setThreadCount(int threadCount)
|
||||
{
|
||||
this.threadCount = threadCount;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize)
|
||||
{
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setMaxItems(int maxItems)
|
||||
{
|
||||
this.maxItems = maxItems;
|
||||
}
|
||||
|
||||
public void setLoggingInterval(int loggingInterval)
|
||||
{
|
||||
this.loggingInterval = loggingInterval;
|
||||
}
|
||||
|
||||
public void setItemsPerTransaction(int itemsPerTransaction)
|
||||
{
|
||||
this.itemsPerTransaction = itemsPerTransaction;
|
||||
}
|
||||
|
||||
public void setMaxParallelRequests(int maxParallelRequests)
|
||||
{
|
||||
this.maxParallelRequests = maxParallelRequests;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
/**
|
||||
* An immutable POJO to represent a bulk cancellation request
|
||||
*/
|
||||
public record BulkCancellationRequest(String reason)
|
||||
{
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
/**
|
||||
* An interface for monitoring the progress of a bulk operation
|
||||
*/
|
||||
public interface BulkMonitor<T>
|
||||
{
|
||||
/**
|
||||
* Update the bulk status
|
||||
*
|
||||
* @param bulkStatus the bulk status
|
||||
*/
|
||||
void updateBulkStatus(T bulkStatus);
|
||||
|
||||
/**
|
||||
* Register a process
|
||||
*
|
||||
* @param nodeRef the node reference
|
||||
* @param processId the process id
|
||||
* @param bulkOperation the bulk operation
|
||||
*/
|
||||
void registerProcess(NodeRef nodeRef, String processId, BulkOperation bulkOperation);
|
||||
|
||||
/**
|
||||
* Get the bulk status
|
||||
*
|
||||
* @param bulkStatusId the bulk status id
|
||||
* @return the bulk status
|
||||
*/
|
||||
T getBulkStatus(String bulkStatusId);
|
||||
|
||||
/**
|
||||
* Cancel a bulk operation
|
||||
*
|
||||
* @param bulkStatusId
|
||||
* @param bulkCancellationRequest
|
||||
*/
|
||||
void cancelBulkOperation(String bulkStatusId, BulkCancellationRequest bulkCancellationRequest);
|
||||
|
||||
/**
|
||||
* Check if a bulk operation is cancelled
|
||||
*
|
||||
* @param bulkStatusId
|
||||
* @return true if the bulk operation is cancelled
|
||||
*/
|
||||
boolean isCancelled(String bulkStatusId);
|
||||
|
||||
/**
|
||||
* Get the bulk cancellation request
|
||||
*
|
||||
* @param bulkStatusId
|
||||
* @return cancellation reason
|
||||
*/
|
||||
BulkCancellationRequest getBulkCancellationRequest(String bulkStatusId);
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
|
||||
/**
|
||||
* An immutable POJO to represent a bulk operation
|
||||
*/
|
||||
public record BulkOperation(Query searchQuery, String operationType) implements Serializable
|
||||
{
|
||||
public BulkOperation
|
||||
{
|
||||
if (operationType == null || searchQuery == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Operation type and search query must not be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* An immutable POJO to represent the progress of a bulk operation
|
||||
*/
|
||||
public record BulkProgress(long totalItems, String processId, AtomicBoolean cancelled, AtomicInteger currentNodeNumber)
|
||||
{
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
/**
|
||||
* An interface for updating the status of a bulk operation
|
||||
*/
|
||||
public interface BulkStatusUpdater extends ApplicationEventPublisher
|
||||
{
|
||||
/**
|
||||
* Update the bulk status
|
||||
*/
|
||||
void update();
|
||||
}
|
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
/**
|
||||
* Default hold bulk monitor implementation
|
||||
*/
|
||||
public class DefaultHoldBulkMonitor extends AbstractLifecycleBean implements HoldBulkMonitor
|
||||
{
|
||||
protected SimpleCache<String, HoldBulkStatus> holdProgressCache;
|
||||
protected SimpleCache<String, BulkCancellationRequest> bulkCancellationsCache;
|
||||
protected SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry;
|
||||
|
||||
@Override
|
||||
public void updateBulkStatus(HoldBulkStatus holdBulkStatus)
|
||||
{
|
||||
holdProgressCache.put(holdBulkStatus.bulkStatusId(), holdBulkStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerProcess(NodeRef holdRef, String processId, BulkOperation bulkOperation)
|
||||
{
|
||||
if (holdRef != null && processId != null)
|
||||
{
|
||||
holdProcessRegistry.put(new Pair<>(holdRef.getId(), processId),
|
||||
new HoldBulkProcessDetails(processId, getCurrentInstanceDetails(), bulkOperation));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoldBulkStatus getBulkStatus(String bulkStatusId)
|
||||
{
|
||||
return holdProgressCache.get(bulkStatusId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelBulkOperation(String bulkStatusId, BulkCancellationRequest bulkCancellationRequest)
|
||||
{
|
||||
bulkCancellationsCache.put(bulkStatusId, bulkCancellationRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled(String bulkStatusId)
|
||||
{
|
||||
return bulkCancellationsCache.contains(bulkStatusId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BulkCancellationRequest getBulkCancellationRequest(String bulkStatusId)
|
||||
{
|
||||
return bulkCancellationsCache.get(bulkStatusId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HoldBulkStatusAndProcessDetails> getBulkStatusesWithProcessDetails(String holdId)
|
||||
{
|
||||
return holdProcessRegistry.getKeys().stream()
|
||||
.filter(holdIdAndBulkStatusId -> holdId.equals(holdIdAndBulkStatusId.getFirst()))
|
||||
.map(holdIdAndBulkStatusId -> holdProcessRegistry.get(holdIdAndBulkStatusId))
|
||||
.filter(Objects::nonNull)
|
||||
.map(createHoldBulkStatusAndProcessDetails())
|
||||
.filter(statusAndProcess -> Objects.nonNull(statusAndProcess.holdBulkStatus()))
|
||||
.sorted(sortBulkStatuses())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoldBulkStatusAndProcessDetails getBulkStatusWithProcessDetails(String holdId, String bulkStatusId)
|
||||
{
|
||||
return Optional.ofNullable(holdProcessRegistry.get(new Pair<>(holdId, bulkStatusId)))
|
||||
.map(createHoldBulkStatusAndProcessDetails())
|
||||
.filter(statusAndProcess -> Objects.nonNull(statusAndProcess.holdBulkStatus()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected String getCurrentInstanceDetails()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Function<HoldBulkProcessDetails, HoldBulkStatusAndProcessDetails> createHoldBulkStatusAndProcessDetails()
|
||||
{
|
||||
return bulkProcessDetails -> new HoldBulkStatusAndProcessDetails(
|
||||
getBulkStatus(bulkProcessDetails.bulkStatusId()), bulkProcessDetails);
|
||||
}
|
||||
|
||||
protected static Comparator<HoldBulkStatusAndProcessDetails> sortBulkStatuses()
|
||||
{
|
||||
return Comparator.<HoldBulkStatusAndProcessDetails, Date>comparing(
|
||||
statusAndProcess -> statusAndProcess.holdBulkStatus().endTime(),
|
||||
Comparator.nullsLast(Comparator.naturalOrder()))
|
||||
.thenComparing(statusAndProcess -> statusAndProcess.holdBulkStatus().startTime(),
|
||||
Comparator.nullsLast(Comparator.naturalOrder()))
|
||||
.reversed();
|
||||
}
|
||||
|
||||
public void setHoldProgressCache(
|
||||
SimpleCache<String, HoldBulkStatus> holdProgressCache)
|
||||
{
|
||||
this.holdProgressCache = holdProgressCache;
|
||||
}
|
||||
|
||||
public void setHoldProcessRegistry(
|
||||
SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry)
|
||||
{
|
||||
this.holdProcessRegistry = holdProcessRegistry;
|
||||
}
|
||||
|
||||
public void setBulkCancellationsCache(
|
||||
SimpleCache<String, BulkCancellationRequest> bulkCancellationsCache)
|
||||
{
|
||||
this.bulkCancellationsCache = bulkCancellationsCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent applicationEvent)
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent applicationEvent)
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkMonitor;
|
||||
|
||||
/**
|
||||
* An interface for monitoring the progress of a bulk hold operation
|
||||
*/
|
||||
public interface HoldBulkMonitor extends BulkMonitor<HoldBulkStatus>
|
||||
{
|
||||
/**
|
||||
* Get the bulk statuses with process details for a hold
|
||||
*
|
||||
* @param holdId the hold id
|
||||
* @return the bulk statuses with process details
|
||||
*/
|
||||
List<HoldBulkStatusAndProcessDetails> getBulkStatusesWithProcessDetails(String holdId);
|
||||
|
||||
/**
|
||||
* Get the bulk status with process details
|
||||
*
|
||||
* @param holdId the hold id
|
||||
* @param bulkStatusId the bulk status id
|
||||
* @return the bulk status with process details
|
||||
*/
|
||||
HoldBulkStatusAndProcessDetails getBulkStatusWithProcessDetails(String holdId, String bulkStatusId);
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
|
||||
/**
|
||||
* A simple immutable POJO to hold the details of a bulk process
|
||||
*/
|
||||
public record HoldBulkProcessDetails(String bulkStatusId, String creatorInstance, BulkOperation bulkOperation) implements Serializable
|
||||
{
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
/**
|
||||
* Interface defining a hold bulk service.
|
||||
*/
|
||||
public interface HoldBulkService
|
||||
{
|
||||
/**
|
||||
* Initiates a bulk operation on a hold.
|
||||
*
|
||||
* @param holdRef The hold reference
|
||||
* @param bulkOperation The bulk operation
|
||||
* @return The initial status of the bulk operation
|
||||
*/
|
||||
HoldBulkStatus execute(NodeRef holdRef, BulkOperation bulkOperation);
|
||||
|
||||
/**
|
||||
* Cancels a bulk operation.
|
||||
*
|
||||
* @param holdRef The hold reference
|
||||
* @param bulkStatusId The bulk status id
|
||||
* @param bulkCancellationRequest The bulk cancellation request
|
||||
*/
|
||||
void cancelBulkOperation(NodeRef holdRef, String bulkStatusId, BulkCancellationRequest bulkCancellationRequest);
|
||||
}
|
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_NAME;
|
||||
import static org.alfresco.rm.rest.api.model.HoldBulkOperationType.ADD;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkBaseService;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkProgress;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
|
||||
import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService;
|
||||
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
|
||||
import org.alfresco.module.org_alfresco_module_rm.hold.HoldService;
|
||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationType;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link HoldBulkService} interface.
|
||||
*/
|
||||
@SuppressWarnings("PMD.PreserveStackTrace")
|
||||
public class HoldBulkServiceImpl extends BulkBaseService<HoldBulkStatus> implements HoldBulkService
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HoldBulkServiceImpl.class);
|
||||
private static final String MSG_ERR_ACCESS_DENIED = "permissions.err_access_denied";
|
||||
|
||||
private HoldService holdService;
|
||||
private CapabilityService capabilityService;
|
||||
private PermissionService permissionService;
|
||||
private NodeService nodeService;
|
||||
|
||||
@Override
|
||||
protected HoldBulkStatus getInitBulkStatus(String processId, long totalItems)
|
||||
{
|
||||
return new HoldBulkStatus(processId, null, null, 0, 0, totalItems, null, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BulkStatusUpdater getBulkStatusUpdater()
|
||||
{
|
||||
return new HoldBulkStatusUpdater((HoldBulkMonitor) bulkMonitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation,
|
||||
BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress)
|
||||
{
|
||||
return new AddToHoldWorkerProvider(bulkOperation, bulkStatusUpdater, bulkProgress,
|
||||
(HoldBulkMonitor) bulkMonitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation,
|
||||
BulkProgress bulkProgress)
|
||||
{
|
||||
try
|
||||
{
|
||||
HoldBulkOperationType holdBulkOperationType = HoldBulkOperationType.valueOf(bulkOperation.operationType()
|
||||
.toUpperCase(Locale.ENGLISH));
|
||||
return switch (holdBulkOperationType)
|
||||
{
|
||||
case ADD -> new AddToHoldWorkerBatch(nodeRef, bulkProgress);
|
||||
};
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
String errorMsg = "Unsupported action type when starting the bulk process: ";
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("{} {}", errorMsg, bulkOperation.operationType(), e);
|
||||
}
|
||||
throw new InvalidArgumentException(errorMsg + bulkOperation.operationType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkPermissions(NodeRef holdRef, BulkOperation bulkOperation)
|
||||
{
|
||||
if (!holdService.isHold(holdRef))
|
||||
{
|
||||
final String holdName = (String) nodeService.getProperty(holdRef, PROP_NAME);
|
||||
throw new InvalidArgumentException(I18NUtil.getMessage("rm.hold.not-hold", holdName), null);
|
||||
}
|
||||
if (ADD.name().equals(bulkOperation.operationType()) && (!AccessStatus.ALLOWED.equals(
|
||||
capabilityService.getCapabilityAccessState(holdRef, RMPermissionModel.ADD_TO_HOLD)) ||
|
||||
permissionService.hasPermission(holdRef, RMPermissionModel.FILING) == AccessStatus.DENIED))
|
||||
{
|
||||
throw new AccessDeniedException(I18NUtil.getMessage(MSG_ERR_ACCESS_DENIED));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelBulkOperation(NodeRef holdRef, String bulkStatusId, BulkCancellationRequest cancellationRequest)
|
||||
{
|
||||
if (bulkMonitor instanceof HoldBulkMonitor holdBulkMonitor)
|
||||
{
|
||||
HoldBulkStatusAndProcessDetails statusAndProcessDetails = holdBulkMonitor.getBulkStatusWithProcessDetails(
|
||||
holdRef.getId(), bulkStatusId);
|
||||
|
||||
Optional.ofNullable(statusAndProcessDetails).map(HoldBulkStatusAndProcessDetails::holdBulkProcessDetails)
|
||||
.map(HoldBulkProcessDetails::bulkOperation).ifPresent(bulkOperation -> {
|
||||
checkPermissions(holdRef, bulkOperation);
|
||||
holdBulkMonitor.cancelBulkOperation(bulkStatusId, cancellationRequest);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class AddToHoldWorkerBatch implements BatchProcessWorker<NodeRef>
|
||||
{
|
||||
private final NodeRef holdRef;
|
||||
private final String currentUser;
|
||||
private final BulkProgress bulkProgress;
|
||||
|
||||
public AddToHoldWorkerBatch(NodeRef holdRef, BulkProgress bulkProgress)
|
||||
{
|
||||
this.holdRef = holdRef;
|
||||
this.bulkProgress = bulkProgress;
|
||||
currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier(NodeRef entry)
|
||||
{
|
||||
return entry.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeProcess()
|
||||
{
|
||||
AuthenticationUtil.pushAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(NodeRef entry) throws Throwable
|
||||
{
|
||||
if (!bulkProgress.cancelled().get())
|
||||
{
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
|
||||
holdService.addToHold(holdRef, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterProcess()
|
||||
{
|
||||
AuthenticationUtil.popAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
private class AddToHoldWorkerProvider implements BatchProcessWorkProvider<NodeRef>
|
||||
{
|
||||
private final HoldBulkMonitor holdBulkMonitor;
|
||||
private final Query searchQuery;
|
||||
private final String currentUser;
|
||||
private final BulkProgress bulkProgress;
|
||||
private final BulkStatusUpdater bulkStatusUpdater;
|
||||
|
||||
public AddToHoldWorkerProvider(BulkOperation bulkOperation,
|
||||
BulkStatusUpdater bulkStatusUpdater, BulkProgress bulkProgress, HoldBulkMonitor holdBulkMonitor)
|
||||
{
|
||||
this.searchQuery = bulkOperation.searchQuery();
|
||||
this.bulkProgress = bulkProgress;
|
||||
this.bulkStatusUpdater = bulkStatusUpdater;
|
||||
this.holdBulkMonitor = holdBulkMonitor;
|
||||
currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalEstimatedWorkSize()
|
||||
{
|
||||
return (int) bulkProgress.totalItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalEstimatedWorkSizeLong()
|
||||
{
|
||||
return bulkProgress.totalItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<NodeRef> getNextWork()
|
||||
{
|
||||
AuthenticationUtil.pushAuthentication();
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
|
||||
if (holdBulkMonitor.isCancelled(bulkProgress.processId()))
|
||||
{
|
||||
bulkProgress.cancelled().set(true);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
SearchParameters searchParams = getNextPageParameters();
|
||||
ResultSet result = searchService.query(searchParams);
|
||||
if (result.getNodeRefs().isEmpty())
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
AuthenticationUtil.popAuthentication();
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Processing the next work for the batch processor, skipCount={}, size={}",
|
||||
searchParams.getSkipCount(), result.getNumberFound());
|
||||
}
|
||||
bulkProgress.currentNodeNumber().addAndGet(batchSize);
|
||||
bulkStatusUpdater.update();
|
||||
return result.getNodeRefs();
|
||||
}
|
||||
|
||||
private SearchParameters getNextPageParameters()
|
||||
{
|
||||
SearchParameters searchParams = new SearchParameters();
|
||||
searchMapper.setDefaults(searchParams);
|
||||
searchMapper.fromQuery(searchParams, searchQuery);
|
||||
searchParams.setSkipCount(bulkProgress.currentNodeNumber().get());
|
||||
searchParams.setMaxItems(batchSize);
|
||||
searchParams.setLimit(batchSize);
|
||||
searchParams.addSort("@" + ContentModel.PROP_CREATED, true);
|
||||
return searchParams;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setHoldService(HoldService holdService)
|
||||
{
|
||||
this.holdService = holdService;
|
||||
}
|
||||
|
||||
public void setCapabilityService(CapabilityService capabilityService)
|
||||
{
|
||||
this.capabilityService = capabilityService;
|
||||
}
|
||||
|
||||
public void setPermissionService(PermissionService permissionService)
|
||||
{
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* An immutable POJO that contains the status of a hold bulk operation
|
||||
*/
|
||||
public record HoldBulkStatus(String bulkStatusId, Date startTime, Date endTime, long processedItems, long errorsCount,
|
||||
long totalItems, String lastError, boolean isCancelled, String cancellationReason)
|
||||
implements Serializable
|
||||
{
|
||||
public enum Status
|
||||
{
|
||||
PENDING("PENDING"),
|
||||
IN_PROGRESS("IN PROGRESS"),
|
||||
DONE("DONE"),
|
||||
CANCELLED("CANCELLED");
|
||||
|
||||
private final String value;
|
||||
|
||||
Status(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
if (isCancelled)
|
||||
{
|
||||
return Status.CANCELLED.getValue();
|
||||
}
|
||||
else if (startTime == null && endTime == null)
|
||||
{
|
||||
return Status.PENDING.getValue();
|
||||
}
|
||||
else if (startTime != null && endTime == null)
|
||||
{
|
||||
return Status.IN_PROGRESS.getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Status.DONE.getValue();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
/**
|
||||
* An immutable POJO that contains the status of a hold bulk operation and the details of the process
|
||||
*/
|
||||
public record HoldBulkStatusAndProcessDetails(HoldBulkStatus holdBulkStatus,
|
||||
HoldBulkProcessDetails holdBulkProcessDetails)
|
||||
{
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkStatusUpdater;
|
||||
import org.alfresco.repo.batch.BatchMonitor;
|
||||
import org.alfresco.repo.batch.BatchMonitorEvent;
|
||||
|
||||
/**
|
||||
* An implementation of {@link BulkStatusUpdater} for the hold bulk operation
|
||||
*/
|
||||
public class HoldBulkStatusUpdater implements BulkStatusUpdater
|
||||
{
|
||||
private final Runnable task;
|
||||
private BatchMonitor batchMonitor;
|
||||
|
||||
public HoldBulkStatusUpdater(HoldBulkMonitor holdBulkMonitor)
|
||||
{
|
||||
this.task = () -> holdBulkMonitor.updateBulkStatus(
|
||||
new HoldBulkStatus(batchMonitor.getProcessName(),
|
||||
batchMonitor.getStartTime(),
|
||||
batchMonitor.getEndTime(),
|
||||
batchMonitor.getSuccessfullyProcessedEntriesLong() + batchMonitor.getTotalErrorsLong(),
|
||||
batchMonitor.getTotalErrorsLong(),
|
||||
batchMonitor.getTotalResultsLong(),
|
||||
batchMonitor.getLastError(),
|
||||
holdBulkMonitor.isCancelled(batchMonitor.getProcessName()),
|
||||
Optional.ofNullable(holdBulkMonitor.getBulkCancellationRequest(batchMonitor.getProcessName()))
|
||||
.map(BulkCancellationRequest::reason)
|
||||
.orElse(null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update()
|
||||
{
|
||||
if (task != null && batchMonitor != null)
|
||||
{
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishEvent(Object event)
|
||||
{
|
||||
if (event instanceof BatchMonitorEvent batchMonitorEvent)
|
||||
{
|
||||
batchMonitor = batchMonitorEvent.getBatchMonitor();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk.hold;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkOperation;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationType;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkStatusEntry;
|
||||
|
||||
/**
|
||||
* Utility class for hold bulk operations
|
||||
*/
|
||||
@SuppressWarnings("PMD.PreserveStackTrace")
|
||||
public final class HoldBulkUtils
|
||||
{
|
||||
private HoldBulkUtils()
|
||||
{
|
||||
}
|
||||
|
||||
public static HoldBulkStatusEntry toHoldBulkStatusEntry(
|
||||
HoldBulkStatusAndProcessDetails holdBulkStatusAndProcessDetails)
|
||||
{
|
||||
HoldBulkStatus bulkStatus = holdBulkStatusAndProcessDetails.holdBulkStatus();
|
||||
BulkOperation bulkOperation = holdBulkStatusAndProcessDetails.holdBulkProcessDetails().bulkOperation();
|
||||
|
||||
try
|
||||
{
|
||||
HoldBulkOperation holdBulkOperation = new HoldBulkOperation(
|
||||
new Query(bulkOperation.searchQuery().getLanguage(),
|
||||
bulkOperation.searchQuery().getQuery(), bulkOperation.searchQuery().getUserQuery()),
|
||||
HoldBulkOperationType.valueOf(bulkOperation.operationType()));
|
||||
return new HoldBulkStatusEntry(bulkStatus.bulkStatusId(), bulkStatus.startTime(),
|
||||
bulkStatus.endTime(), bulkStatus.processedItems(), bulkStatus.errorsCount(),
|
||||
bulkStatus.totalItems(), bulkStatus.lastError(), bulkStatus.getStatus(),
|
||||
bulkStatus.cancellationReason(), holdBulkOperation);
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
String errorMsg = "Unsupported action type in the bulk operation: ";
|
||||
throw new InvalidArgumentException(errorMsg + bulkOperation.operationType());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #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.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkService;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatusAndProcessDetails;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkUtils;
|
||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
||||
import org.alfresco.rest.framework.Operation;
|
||||
import org.alfresco.rest.framework.WebApiDescription;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
|
||||
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.rest.framework.webscripts.WithResponse;
|
||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
|
||||
import org.alfresco.rm.rest.api.model.BulkCancellationEntry;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkStatusEntry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
@RelationshipResource(name = "bulk-statuses", entityResource = HoldsEntityResource.class, title = "Bulk statuses of a hold")
|
||||
public class HoldsBulkStatusesRelation
|
||||
implements RelationshipResourceAction.Read<HoldBulkStatusEntry>,
|
||||
RelationshipResourceAction.ReadById<HoldBulkStatusEntry>
|
||||
{
|
||||
private HoldBulkMonitor holdBulkMonitor;
|
||||
private HoldBulkService holdBulkService;
|
||||
private FilePlanComponentsApiUtils apiUtils;
|
||||
private PermissionService permissionService;
|
||||
|
||||
@Override
|
||||
public CollectionWithPagingInfo<HoldBulkStatusEntry> readAll(String holdId, Parameters parameters)
|
||||
{
|
||||
// validate parameters
|
||||
checkNotBlank("holdId", holdId);
|
||||
mandatory("parameters", parameters);
|
||||
|
||||
NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
|
||||
|
||||
checkReadPermissions(holdRef);
|
||||
|
||||
List<HoldBulkStatusAndProcessDetails> statuses = holdBulkMonitor.getBulkStatusesWithProcessDetails(holdId);
|
||||
List<HoldBulkStatusEntry> page = statuses.stream()
|
||||
.map(HoldBulkUtils::toHoldBulkStatusEntry)
|
||||
.skip(parameters.getPaging().getSkipCount())
|
||||
.limit(parameters.getPaging().getMaxItems())
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
|
||||
int totalItems = statuses.size();
|
||||
boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
|
||||
return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoldBulkStatusEntry readById(String holdId, String bulkStatusId, Parameters parameters)
|
||||
throws RelationshipResourceNotFoundException
|
||||
{
|
||||
checkNotBlank("holdId", holdId);
|
||||
checkNotBlank("bulkStatusId", bulkStatusId);
|
||||
mandatory("parameters", parameters);
|
||||
|
||||
NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
|
||||
|
||||
checkReadPermissions(holdRef);
|
||||
|
||||
return Optional.ofNullable(holdBulkMonitor.getBulkStatusWithProcessDetails(holdId, bulkStatusId))
|
||||
.map(HoldBulkUtils::toHoldBulkStatusEntry)
|
||||
.orElseThrow(() -> new EntityNotFoundException(bulkStatusId));
|
||||
}
|
||||
|
||||
@Operation("cancel")
|
||||
@WebApiDescription(title = "Cancel a bulk operation",
|
||||
successStatus = HttpServletResponse.SC_OK)
|
||||
public void cancelBulkOperation(String holdId, String bulkStatusId, BulkCancellationEntry bulkCancellationEntry,
|
||||
Parameters parameters,
|
||||
WithResponse withResponse)
|
||||
{
|
||||
checkNotBlank("holdId", holdId);
|
||||
checkNotBlank("bulkStatusId", bulkStatusId);
|
||||
mandatory("parameters", parameters);
|
||||
|
||||
NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
|
||||
|
||||
checkReadPermissions(holdRef);
|
||||
|
||||
if (holdBulkMonitor.getBulkStatus(bulkStatusId) == null)
|
||||
{
|
||||
throw new NotFoundException("Bulk status not found");
|
||||
}
|
||||
|
||||
holdBulkService.cancelBulkOperation(holdRef, bulkStatusId, new BulkCancellationRequest(bulkCancellationEntry.reason()));
|
||||
}
|
||||
|
||||
private void checkReadPermissions(NodeRef holdRef)
|
||||
{
|
||||
if (permissionService.hasReadPermission(holdRef) == AccessStatus.DENIED)
|
||||
{
|
||||
throw new PermissionDeniedException(I18NUtil.getMessage("permissions.err_access_denied"));
|
||||
}
|
||||
}
|
||||
|
||||
public void setHoldBulkMonitor(HoldBulkMonitor holdBulkMonitor)
|
||||
{
|
||||
this.holdBulkMonitor = holdBulkMonitor;
|
||||
}
|
||||
|
||||
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
|
||||
{
|
||||
this.apiUtils = apiUtils;
|
||||
}
|
||||
|
||||
public void setPermissionService(PermissionService permissionService)
|
||||
{
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
public void setHoldBulkService(HoldBulkService holdBulkService)
|
||||
{
|
||||
this.holdBulkService = holdBulkService;
|
||||
}
|
||||
}
|
@@ -30,6 +30,8 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c
|
||||
import static org.alfresco.util.ParameterCheck.mandatory;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkService;
|
||||
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;
|
||||
@@ -42,6 +44,9 @@ 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.HoldBulkOperation;
|
||||
import org.alfresco.rm.rest.api.model.HoldBulkOperationEntry;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus;
|
||||
import org.alfresco.rm.rest.api.model.HoldDeletionReason;
|
||||
import org.alfresco.rm.rest.api.model.HoldModel;
|
||||
import org.alfresco.service.cmr.model.FileFolderService;
|
||||
@@ -68,6 +73,7 @@ public class HoldsEntityResource implements
|
||||
private ApiNodesModelFactory nodesModelFactory;
|
||||
private HoldService holdService;
|
||||
private TransactionService transactionService;
|
||||
private HoldBulkService holdBulkService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
@@ -157,6 +163,23 @@ public class HoldsEntityResource implements
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Operation("bulk")
|
||||
@WebApiDescription(title = "Start the hold bulk operation",
|
||||
successStatus = HttpServletResponse.SC_ACCEPTED)
|
||||
public HoldBulkOperationEntry bulk(String holdId, HoldBulkOperation holdBulkOperation, Parameters parameters,
|
||||
WithResponse withResponse)
|
||||
{
|
||||
// validate parameters
|
||||
checkNotBlank("holdId", holdId);
|
||||
mandatory("parameters", parameters);
|
||||
|
||||
NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
|
||||
|
||||
HoldBulkStatus holdBulkStatus = holdBulkService.execute(parentNodeRef,
|
||||
new BulkOperation(holdBulkOperation.query(), holdBulkOperation.op().name()));
|
||||
return new HoldBulkOperationEntry(holdBulkStatus.bulkStatusId(), holdBulkStatus.totalItems());
|
||||
}
|
||||
|
||||
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
|
||||
{
|
||||
this.apiUtils = apiUtils;
|
||||
@@ -181,4 +204,9 @@ public class HoldsEntityResource implements
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
public void setHoldBulkService(HoldBulkService holdBulkService)
|
||||
{
|
||||
this.holdBulkService = holdBulkService;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rm.rest.api.model;
|
||||
|
||||
public record BulkCancellationEntry(String reason) {}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rm.rest.api.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
|
||||
public record HoldBulkOperation(@JsonProperty(required = true) Query query, @JsonProperty(required = true) HoldBulkOperationType op) {}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rm.rest.api.model;
|
||||
|
||||
public record HoldBulkOperationEntry(String bulkStatusId, long totalItems){}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rm.rest.api.model;
|
||||
|
||||
/**
|
||||
* This enum represents the types of bulk operations that can be performed on holds
|
||||
*/
|
||||
public enum HoldBulkOperationType
|
||||
{
|
||||
/**
|
||||
* The ADD operation represents adding items to a hold in bulk.
|
||||
*/
|
||||
ADD
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rm.rest.api.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public record HoldBulkStatusEntry(String bulkStatusId, Date startTime, Date endTime, long processedItems, long errorsCount,
|
||||
long totalItems, String lastError, String status, String cancellationReason, HoldBulkOperation holdBulkOperation) {
|
||||
}
|
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.test.integration.bulk.hold;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkCancellationRequest;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkServiceImpl;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus.Status;
|
||||
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
|
||||
import org.alfresco.rest.api.search.model.Query;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.ResultSet;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.extensions.webscripts.GUID;
|
||||
|
||||
/**
|
||||
* Hold bulk service integration test.
|
||||
*/
|
||||
@SuppressWarnings({ "PMD.TestClassWithoutTestCases", "PMD.JUnit4TestShouldUseTestAnnotation" })
|
||||
public class HoldBulkServiceTest extends BaseRMTestCase
|
||||
{
|
||||
private static final int RECORD_COUNT = 10;
|
||||
private final SearchService searchServiceMock = mock(SearchService.class);
|
||||
private final ResultSet resultSet = mock(ResultSet.class);
|
||||
private HoldBulkServiceImpl holdBulkService;
|
||||
private HoldBulkMonitor holdBulkMonitor;
|
||||
|
||||
@Override
|
||||
protected void initServices()
|
||||
{
|
||||
super.initServices();
|
||||
holdBulkMonitor = (HoldBulkMonitor) applicationContext.getBean("holdBulkMonitor");
|
||||
holdBulkService = (HoldBulkServiceImpl) applicationContext.getBean("holdBulkService");
|
||||
holdBulkService.setSearchService(searchServiceMock);
|
||||
Mockito.when(searchServiceMock.query(any(SearchParameters.class))).thenReturn(resultSet);
|
||||
}
|
||||
|
||||
public void testCancelBulkOperation()
|
||||
{
|
||||
doBehaviourDrivenTest(new BehaviourDrivenTest()
|
||||
{
|
||||
private NodeRef hold;
|
||||
private HoldBulkStatus holdBulkStatus;
|
||||
private final ResultSet resultSet = mock(ResultSet.class);
|
||||
|
||||
public void given()
|
||||
{
|
||||
Mockito.when(resultSet.getNumberFound()).thenReturn(4L);
|
||||
Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(true).thenReturn(false);
|
||||
Mockito.when(resultSet.getNodeRefs())
|
||||
.thenAnswer((Answer<List<NodeRef>>) invocationOnMock -> {
|
||||
await().pollDelay(1, SECONDS).until(() -> true);
|
||||
return List.of(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()),
|
||||
new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()));
|
||||
});
|
||||
// create a hold
|
||||
hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate());
|
||||
}
|
||||
|
||||
public void when()
|
||||
{
|
||||
BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD");
|
||||
// execute the bulk operation
|
||||
holdBulkStatus = holdBulkService.execute(hold, bulkOperation);
|
||||
// cancel the bulk operation
|
||||
holdBulkMonitor.cancelBulkOperation(holdBulkStatus.bulkStatusId(),
|
||||
new BulkCancellationRequest("No reason"));
|
||||
await().atMost(10, SECONDS)
|
||||
.until(() -> Objects.equals(
|
||||
holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
Status.CANCELLED.getValue()));
|
||||
}
|
||||
|
||||
public void then()
|
||||
{
|
||||
holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId());
|
||||
assertNotNull(holdBulkStatus.startTime());
|
||||
assertNotNull(holdBulkStatus.endTime());
|
||||
assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
HoldBulkStatus.Status.CANCELLED.getValue());
|
||||
assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).cancellationReason(),
|
||||
"No reason");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testAddRecordsToHoldViaBulk()
|
||||
{
|
||||
doBehaviourDrivenTest(new BehaviourDrivenTest()
|
||||
{
|
||||
private NodeRef hold;
|
||||
private NodeRef recordFolder;
|
||||
private HoldBulkStatus holdBulkStatus;
|
||||
private final List<NodeRef> records = new ArrayList<>(RECORD_COUNT);
|
||||
|
||||
public void given()
|
||||
{
|
||||
Mockito.when(resultSet.getNumberFound()).thenReturn(Long.valueOf(RECORD_COUNT));
|
||||
Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(false);
|
||||
// create a hold
|
||||
hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate());
|
||||
|
||||
// create a record folder that contains records
|
||||
NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, GUID.generate());
|
||||
recordFolder = recordFolderService.createRecordFolder(recordCategory, GUID.generate());
|
||||
for (int i = 0; i < RECORD_COUNT; i++)
|
||||
{
|
||||
records.add(
|
||||
recordService.createRecordFromContent(recordFolder, GUID.generate(), ContentModel.TYPE_CONTENT,
|
||||
null, null));
|
||||
}
|
||||
Mockito.when(resultSet.getNodeRefs()).thenReturn(records).thenReturn(records)
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
// assert current states
|
||||
assertFalse(freezeService.isFrozen(recordFolder));
|
||||
assertFalse(freezeService.hasFrozenChildren(recordFolder));
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
assertFalse(freezeService.isFrozen(record));
|
||||
}
|
||||
|
||||
// additional check for child held caching
|
||||
assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN));
|
||||
assertEquals(0, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT));
|
||||
}
|
||||
|
||||
public void when()
|
||||
{
|
||||
BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD");
|
||||
// execute the bulk operation
|
||||
holdBulkStatus = holdBulkService.execute(hold, bulkOperation);
|
||||
await().atMost(10, SECONDS)
|
||||
.until(() -> Objects.equals(
|
||||
holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
Status.DONE.getValue()));
|
||||
}
|
||||
|
||||
public void then()
|
||||
{
|
||||
holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId());
|
||||
assertNotNull(holdBulkStatus.startTime());
|
||||
assertNotNull(holdBulkStatus.endTime());
|
||||
assertEquals(RECORD_COUNT, holdBulkStatus.totalItems());
|
||||
assertEquals(RECORD_COUNT, holdBulkStatus.processedItems());
|
||||
assertEquals(0, holdBulkStatus.errorsCount());
|
||||
assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
HoldBulkStatus.Status.DONE.getValue());
|
||||
|
||||
// record is held
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
assertTrue(freezeService.isFrozen(record));
|
||||
}
|
||||
|
||||
// record folder has frozen children
|
||||
assertFalse(freezeService.isFrozen(recordFolder));
|
||||
assertTrue(freezeService.hasFrozenChildren(recordFolder));
|
||||
|
||||
// record folder is not held
|
||||
assertFalse(holdService.getHeld(hold).contains(recordFolder));
|
||||
assertFalse(holdService.heldBy(recordFolder, true).contains(hold));
|
||||
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
// hold contains record
|
||||
assertTrue(holdService.getHeld(hold).contains(record));
|
||||
assertTrue(holdService.heldBy(record, true).contains(hold));
|
||||
}
|
||||
|
||||
// additional check for child held caching
|
||||
assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN));
|
||||
assertEquals(RECORD_COUNT, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testAddRecordFolderToHoldViaBulk()
|
||||
{
|
||||
doBehaviourDrivenTest(new BehaviourDrivenTest()
|
||||
{
|
||||
private NodeRef hold;
|
||||
private NodeRef recordFolder;
|
||||
private final List<NodeRef> records = new ArrayList<>(RECORD_COUNT);
|
||||
private HoldBulkStatus holdBulkStatus;
|
||||
|
||||
public void given()
|
||||
{
|
||||
Mockito.when(resultSet.getNumberFound()).thenReturn(1L);
|
||||
Mockito.when(resultSet.hasMore()).thenReturn(false).thenReturn(false);
|
||||
// create a hold
|
||||
hold = holdService.createHold(filePlan, GUID.generate(), GUID.generate(), GUID.generate());
|
||||
|
||||
// create a record folder that contains records
|
||||
NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, GUID.generate());
|
||||
recordFolder = recordFolderService.createRecordFolder(recordCategory, GUID.generate());
|
||||
for (int i = 0; i < RECORD_COUNT; i++)
|
||||
{
|
||||
records.add(
|
||||
recordService.createRecordFromContent(recordFolder, GUID.generate(), ContentModel.TYPE_CONTENT,
|
||||
null, null));
|
||||
}
|
||||
Mockito.when(resultSet.getNodeRefs()).thenReturn(Collections.singletonList(recordFolder))
|
||||
.thenReturn(Collections.singletonList(recordFolder)).thenReturn(Collections.emptyList());
|
||||
|
||||
// assert current states
|
||||
assertFalse(freezeService.isFrozen(recordFolder));
|
||||
assertFalse(freezeService.hasFrozenChildren(recordFolder));
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
assertFalse(freezeService.isFrozen(record));
|
||||
}
|
||||
|
||||
// additional check for child held caching
|
||||
assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN));
|
||||
assertEquals(0, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT));
|
||||
}
|
||||
|
||||
public void when()
|
||||
{
|
||||
BulkOperation bulkOperation = new BulkOperation(new Query("afts", "*", ""), "ADD");
|
||||
// execute the bulk operation
|
||||
holdBulkStatus = holdBulkService.execute(hold, bulkOperation);
|
||||
await().atMost(10, SECONDS)
|
||||
.until(() -> Objects.equals(
|
||||
holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
Status.DONE.getValue()));
|
||||
}
|
||||
|
||||
public void then()
|
||||
{
|
||||
holdBulkStatus = holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId());
|
||||
assertNotNull(holdBulkStatus.startTime());
|
||||
assertNotNull(holdBulkStatus.endTime());
|
||||
assertEquals(1, holdBulkStatus.totalItems());
|
||||
assertEquals(1, holdBulkStatus.processedItems());
|
||||
assertEquals(0, holdBulkStatus.errorsCount());
|
||||
assertEquals(holdBulkMonitor.getBulkStatus(holdBulkStatus.bulkStatusId()).getStatus(),
|
||||
HoldBulkStatus.Status.DONE.getValue());
|
||||
|
||||
for (NodeRef record : records)
|
||||
{
|
||||
// record is held
|
||||
assertTrue(freezeService.isFrozen(record));
|
||||
assertFalse(holdService.getHeld(hold).contains(record));
|
||||
assertTrue(holdService.heldBy(record, true).contains(hold));
|
||||
}
|
||||
|
||||
// record folder has frozen children
|
||||
assertTrue(freezeService.isFrozen(recordFolder));
|
||||
assertTrue(freezeService.hasFrozenChildren(recordFolder));
|
||||
|
||||
// hold contains record folder
|
||||
assertTrue(holdService.getHeld(hold).contains(recordFolder));
|
||||
assertTrue(holdService.heldBy(recordFolder, true).contains(hold));
|
||||
|
||||
// additional check for child held caching
|
||||
assertTrue(nodeService.hasAspect(recordFolder, ASPECT_HELD_CHILDREN));
|
||||
assertEquals(RECORD_COUNT, nodeService.getProperty(recordFolder, PROP_HELD_CHILDREN_COUNT));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* #%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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.bulk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.DefaultHoldBulkMonitor;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkProcessDetails;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatus;
|
||||
import org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkStatusAndProcessDetails;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
public class DefaultHoldBulkMonitorUnitTest
|
||||
{
|
||||
|
||||
@Mock
|
||||
private SimpleCache<String, HoldBulkStatus> holdProgressCache;
|
||||
|
||||
@Mock
|
||||
private SimpleCache<Pair<String, String>, HoldBulkProcessDetails> holdProcessRegistry;
|
||||
|
||||
private DefaultHoldBulkMonitor holdBulkMonitor;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
MockitoAnnotations.openMocks(this);
|
||||
holdBulkMonitor = new DefaultHoldBulkMonitor();
|
||||
holdBulkMonitor.setHoldProgressCache(holdProgressCache);
|
||||
holdBulkMonitor.setHoldProcessRegistry(holdProcessRegistry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateBulkStatus()
|
||||
{
|
||||
HoldBulkStatus status = new HoldBulkStatus("bulkStatusId", null, null, 0L, 0L, 0L, null, false, null);
|
||||
|
||||
holdBulkMonitor.updateBulkStatus(status);
|
||||
|
||||
Mockito.verify(holdProgressCache).put("bulkStatusId", status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterProcess()
|
||||
{
|
||||
NodeRef holdRef = new NodeRef("workspace://SpacesStore/holdId");
|
||||
String processId = "processId";
|
||||
when(holdProcessRegistry.get(new Pair<>(holdRef.getId(), processId))).thenReturn(null);
|
||||
|
||||
holdBulkMonitor.registerProcess(holdRef, processId, null);
|
||||
|
||||
Mockito.verify(holdProcessRegistry)
|
||||
.put(new Pair<>(holdRef.getId(), processId), new HoldBulkProcessDetails(processId, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBulkStatusesWithProcessDetailsReturnsEmptyListWhenNoProcessesWithProcessDetails()
|
||||
{
|
||||
when(holdProcessRegistry.getKeys()).thenReturn(Collections.emptyList());
|
||||
assertEquals(Collections.emptyList(), holdBulkMonitor.getBulkStatusesWithProcessDetails("holdId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBulkStatus()
|
||||
{
|
||||
BulkOperation bulkOperation = mock(BulkOperation.class);
|
||||
HoldBulkStatus status1 = new HoldBulkStatus("process1", new Date(1000), new Date(2000), 0L, 0L, 0L, null, false,
|
||||
null);
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process1", null, bulkOperation));
|
||||
when(holdProgressCache.get("process1")).thenReturn(status1);
|
||||
|
||||
assertEquals(new HoldBulkStatusAndProcessDetails(status1,
|
||||
new HoldBulkProcessDetails(status1.bulkStatusId(), null, bulkOperation)),
|
||||
holdBulkMonitor.getBulkStatusWithProcessDetails("holdId", "process1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNonExistingBulkStatus()
|
||||
{
|
||||
BulkOperation bulkOperation = mock(BulkOperation.class);
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process1", null, bulkOperation));
|
||||
when(holdProgressCache.get("process1")).thenReturn(null);
|
||||
|
||||
assertNull(holdBulkMonitor.getBulkStatusWithProcessDetails("holdId", "process1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBulkStatusesForHoldReturnsSortedStatusesWithProcessDetails()
|
||||
{
|
||||
BulkOperation bulkOperation = mock(BulkOperation.class);
|
||||
HoldBulkStatus status1 = new HoldBulkStatus("process1", new Date(1000), new Date(2000), 0L, 0L, 0L, null, false,
|
||||
null);
|
||||
HoldBulkStatus status2 = new HoldBulkStatus("process2", new Date(3000), null, 0L, 0L, 0L, null, false, null);
|
||||
HoldBulkStatus status3 = new HoldBulkStatus("process3", new Date(4000), null, 0L, 0L, 0L, null, false, null);
|
||||
HoldBulkStatus status4 = new HoldBulkStatus("process4", new Date(500), new Date(800), 0L, 0L, 0L, null, false,
|
||||
null);
|
||||
HoldBulkStatus status5 = new HoldBulkStatus("process5", null, null, 0L, 0L, 0L, null, false, null);
|
||||
|
||||
when(holdProcessRegistry.getKeys()).thenReturn(
|
||||
Arrays.asList(new Pair<>("holdId", "process1"), new Pair<>("holdId", "process2"),
|
||||
new Pair<>("holdId", "process3"), new Pair<>("holdId", "process4"), new Pair<>("holdId", "process5"))
|
||||
);
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process1"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process1", null, bulkOperation));
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process2"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process2", null, bulkOperation));
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process3"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process3", null, bulkOperation));
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process4"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process4", null, bulkOperation));
|
||||
when(holdProcessRegistry.get(new Pair<>("holdId", "process5"))).thenReturn(
|
||||
new HoldBulkProcessDetails("process5", null, bulkOperation));
|
||||
when(holdProgressCache.get("process1")).thenReturn(status1);
|
||||
when(holdProgressCache.get("process2")).thenReturn(status2);
|
||||
when(holdProgressCache.get("process3")).thenReturn(status3);
|
||||
when(holdProgressCache.get("process4")).thenReturn(status4);
|
||||
when(holdProgressCache.get("process5")).thenReturn(status5);
|
||||
|
||||
assertEquals(Arrays.asList(status5, status3, status2, status1, status4).stream().map(
|
||||
status -> new HoldBulkStatusAndProcessDetails(status,
|
||||
new HoldBulkProcessDetails(status.bulkStatusId(), null, bulkOperation))).toList(),
|
||||
holdBulkMonitor.getBulkStatusesWithProcessDetails("holdId"));
|
||||
}
|
||||
}
|
@@ -2314,6 +2314,145 @@ paths:
|
||||
description: Unexpected error
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'/holds/{holdId}/bulk-statuses':
|
||||
get:
|
||||
tags:
|
||||
- holds
|
||||
operationId: listBulkStatuses
|
||||
summary: Get bulk statuses
|
||||
description: |
|
||||
Gets bulk statuses for hold with id **holdId**.
|
||||
parameters:
|
||||
- $ref: '#/parameters/holdIdParam'
|
||||
- $ref: '#/parameters/skipCountParam'
|
||||
- $ref: '#/parameters/maxItemsParam'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
schema:
|
||||
$ref: '#/definitions/HoldBulkStatusPaging'
|
||||
'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'
|
||||
'/holds/{holdId}/bulk-statuses/{bulkStatusId}':
|
||||
get:
|
||||
tags:
|
||||
- holds
|
||||
operationId: getBulkStatus
|
||||
summary: Get a bulk status
|
||||
description: |
|
||||
Gets a bulk status specified by **bulkStatusId** for **holdId**.
|
||||
parameters:
|
||||
- $ref: '#/parameters/holdIdParam'
|
||||
- $ref: '#/parameters/bulkStatusId'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
schema:
|
||||
$ref: '#/definitions/HoldBulkStatus'
|
||||
'400':
|
||||
description: |
|
||||
Invalid parameter: **holdId** or **bulkStatusId** is not a valid format
|
||||
'401':
|
||||
description: Authentication failed
|
||||
'403':
|
||||
description: Current user does not have permission to read **holdId**
|
||||
'404':
|
||||
description: "**holdId** or **bulkStatusId** does not exist"
|
||||
default:
|
||||
description: Unexpected error
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'/holds/{holdId}/bulk-statuses/{bulkStatusId}/cancel':
|
||||
post:
|
||||
tags:
|
||||
- holds
|
||||
operationId: cancelBulkStatus
|
||||
summary: Cancel the bulk operation
|
||||
description: |
|
||||
Cancels the bulk operation specified by **bulkStatusId** for **holdId**.
|
||||
parameters:
|
||||
- $ref: '#/parameters/holdIdParam'
|
||||
- $ref: '#/parameters/bulkStatusId'
|
||||
- in: body
|
||||
name: cancelReason
|
||||
description: Cancel reason.
|
||||
required: false
|
||||
schema:
|
||||
$ref: '#/definitions/BulkBodyCancel'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
'400':
|
||||
description: |
|
||||
Invalid parameter: **holdId** or **bulkStatusId** is not a valid format
|
||||
'401':
|
||||
description: Authentication failed
|
||||
'403':
|
||||
description: Current user does not have permission to cancel the bulk process for **holdId** and **bulkStatusId**
|
||||
'404':
|
||||
description: "**holdId** or **bulkStatusId** does not exist"
|
||||
default:
|
||||
description: Unexpected error
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'/holds/{holdId}/bulk':
|
||||
post:
|
||||
tags:
|
||||
- holds
|
||||
operationId: startHoldBulkProcess
|
||||
summary: Start the hold bulk process
|
||||
description: |
|
||||
Start the asynchronous bulk process for a hold with id **holdId** based on search query results.
|
||||
|
||||
```JSON
|
||||
For example, the following JSON body starts the bulk process to add search query results
|
||||
as children of a hold.
|
||||
|
||||
{
|
||||
"query": {
|
||||
"query": "SITE:swsdp and TYPE:content",
|
||||
"language": "afts"
|
||||
},
|
||||
"op": "ADD"
|
||||
}
|
||||
```
|
||||
parameters:
|
||||
- $ref: '#/parameters/holdIdParam'
|
||||
- in: body
|
||||
name: holdBulkOperation
|
||||
description: Bulk operation.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/HoldBulkOperation'
|
||||
responses:
|
||||
'202':
|
||||
description: Successful response
|
||||
schema:
|
||||
$ref: '#/definitions/HoldBulkOperationEntry'
|
||||
'400':
|
||||
description: |
|
||||
Invalid parameter: **holdId** is not a valid format or **HoldBulkOperation** is not valid
|
||||
'401':
|
||||
description: Authentication failed
|
||||
'403':
|
||||
description: Current user does not have permission to start the bulk process for **holdId**
|
||||
'404':
|
||||
description: "**holdId** does not exist"
|
||||
default:
|
||||
description: Unexpected error
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
'/holds/{holdId}/delete':
|
||||
post:
|
||||
tags:
|
||||
@@ -2862,6 +3001,12 @@ parameters:
|
||||
description: The identifier of a child of a hold.
|
||||
required: true
|
||||
type: string
|
||||
bulkStatusId:
|
||||
name: bulkStatusId
|
||||
in: path
|
||||
description: The identifier of a bulk process.
|
||||
required: true
|
||||
type: string
|
||||
## Record
|
||||
recordIdParam:
|
||||
name: recordId
|
||||
@@ -4018,6 +4163,101 @@ definitions:
|
||||
properties:
|
||||
reason:
|
||||
type: string
|
||||
SearchRequestQuery:
|
||||
type: object
|
||||
required:
|
||||
- query
|
||||
properties:
|
||||
language:
|
||||
description: The query language in which the query is written.
|
||||
type: string
|
||||
default: afts
|
||||
enum:
|
||||
- afts
|
||||
- lucene
|
||||
- cmis
|
||||
userQuery:
|
||||
description: The search request typed in by the user
|
||||
type: string
|
||||
query:
|
||||
description: The query which may have been generated in some way from the userQuery
|
||||
type: string
|
||||
HoldBulkOperation:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
$ref: '#/definitions/SearchRequestQuery'
|
||||
op:
|
||||
description: The operation type.
|
||||
type: string
|
||||
default: ADD
|
||||
enum:
|
||||
- ADD
|
||||
HoldBulkOperationEntry:
|
||||
type: object
|
||||
properties:
|
||||
bulkStatusId:
|
||||
type: string
|
||||
totalItems:
|
||||
type: integer
|
||||
format: int64
|
||||
BulkBodyCancel:
|
||||
type: object
|
||||
properties:
|
||||
reason:
|
||||
type: string
|
||||
HoldBulkStatus:
|
||||
type: object
|
||||
properties:
|
||||
bulkStatusId:
|
||||
type: string
|
||||
startTime:
|
||||
type: string
|
||||
format: date-time
|
||||
endTime:
|
||||
type: string
|
||||
format: date-time
|
||||
processedItems:
|
||||
type: integer
|
||||
format: int64
|
||||
errorsCount:
|
||||
type: integer
|
||||
format: int64
|
||||
totalItems:
|
||||
type: integer
|
||||
format: int64
|
||||
lastError:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- PENDING
|
||||
- IN PROGRESS
|
||||
- DONE
|
||||
- CANCELLED
|
||||
cancellationReason:
|
||||
type: string
|
||||
holdBulkOperation:
|
||||
$ref: '#/definitions/HoldBulkOperation'
|
||||
HoldBulkStatusEntry:
|
||||
type: object
|
||||
required:
|
||||
- entry
|
||||
properties:
|
||||
entry:
|
||||
$ref: '#/definitions/HoldBulkStatus'
|
||||
HoldBulkStatusPaging:
|
||||
type: object
|
||||
properties:
|
||||
list:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
$ref: '#/definitions/Pagination'
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/HoldBulkStatusEntry'
|
||||
##
|
||||
RequestBodyFile:
|
||||
type: object
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* alfresco-tas-restapi
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* 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
|
||||
@@ -25,6 +25,8 @@
|
||||
*/
|
||||
package org.alfresco.rest.search;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.alfresco.rest.core.IRestModel;
|
||||
@@ -94,5 +96,27 @@ public class RestRequestQueryModel extends TestModel implements IRestModel<RestR
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RestRequestQueryModel that = (RestRequestQueryModel) o;
|
||||
return Objects.equals(model, that.model) && Objects.equals(getLanguage(), that.getLanguage())
|
||||
&& Objects.equals(getUserQuery(), that.getUserQuery()) && Objects.equals(getQuery(),
|
||||
that.getQuery());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(model, getLanguage(), getUserQuery(), getQuery());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,15 +26,17 @@
|
||||
|
||||
package org.alfresco.rest.api.search.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* POJO class representing the query element of the JSON body
|
||||
**/
|
||||
public class Query
|
||||
public class Query implements Serializable
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 8443756988747629114L;
|
||||
private final String language;
|
||||
private final String query;
|
||||
private final String userQuery;
|
||||
|
Reference in New Issue
Block a user