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:
Damian Ujma
2024-06-19 12:44:07 +02:00
committed by GitHub
parent 2461b75f61
commit 2f66f068a6
44 changed files with 3637 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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