mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			23.3.0.51
			...
			feature/AC
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8986d03a2f | ||
| 
						 | 
					502c996c9e | ||
| 
						 | 
					a22e7d23f0 | 
@@ -139,3 +139,11 @@ content.metadata.async.extract.6.enabled=false
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Max number of entries returned in Record search view
 | 
					# Max number of entries returned in Record search view
 | 
				
			||||||
rm.recordSearch.maxItems=500
 | 
					rm.recordSearch.maxItems=500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Hold bulk
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					rm.hold.bulk.threadCount=2
 | 
				
			||||||
 | 
					rm.hold.bulk.maxItems=1000
 | 
				
			||||||
 | 
					rm.hold.bulk.batchSize=100
 | 
				
			||||||
 | 
					rm.hold.bulk.logging.interval.ms=1000
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,13 @@
 | 
				
			|||||||
      <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
					      <property name="nodesModelFactory" ref="nodesModelFactory" />
 | 
				
			||||||
      <property name="fileFolderService" ref="FileFolderService" />
 | 
					      <property name="fileFolderService" ref="FileFolderService" />
 | 
				
			||||||
      <property name="transactionService" ref="transactionService" />
 | 
					      <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="apiUtils" ref="apiUtils" />
 | 
				
			||||||
 | 
					      <property name="permissionService" ref="PermissionService" />
 | 
				
			||||||
   </bean>
 | 
					   </bean>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   <bean class="org.alfresco.rm.rest.api.holds.HoldsChildrenRelation">
 | 
					   <bean class="org.alfresco.rm.rest.api.holds.HoldsChildrenRelation">
 | 
				
			||||||
@@ -257,4 +264,42 @@
 | 
				
			|||||||
      <property name="beanName" value="restJsonModule" />
 | 
					      <property name="beanName" value="restJsonModule" />
 | 
				
			||||||
      <property name="extendingBeanName" value="rm.restJsonModule" />
 | 
					      <property name="extendingBeanName" value="rm.restJsonModule" />
 | 
				
			||||||
   </bean>
 | 
					   </bean>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   <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="loggingIntervalMs">
 | 
				
			||||||
 | 
					         <value>${rm.hold.bulk.logging.interval.ms}</value>
 | 
				
			||||||
 | 
					      </property>
 | 
				
			||||||
 | 
					   </bean>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   <bean id="holdBulkMonitor" class="org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor">
 | 
				
			||||||
 | 
					      <property name="holdProgressCache" ref="holdProgressCache" />
 | 
				
			||||||
 | 
					      <property name="holdProcessRegistry" ref="holdProcessRegistry" />
 | 
				
			||||||
 | 
					   </bean>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   <bean name="holdProgressCache" factory-bean="cacheFactory" factory-method="createCache">
 | 
				
			||||||
 | 
					      <constructor-arg value="cache.workerRegistryCache" />
 | 
				
			||||||
 | 
					   </bean>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   <bean name="holdProcessRegistry" factory-bean="cacheFactory" factory-method="createCache">
 | 
				
			||||||
 | 
					      <constructor-arg value="cache.workerRegistryCache" />
 | 
				
			||||||
 | 
					   </bean>
 | 
				
			||||||
</beans>
 | 
					</beans>
 | 
				
			||||||
@@ -0,0 +1,207 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%L
 | 
				
			||||||
 | 
					 * Alfresco Records Management Module
 | 
				
			||||||
 | 
					 * %%
 | 
				
			||||||
 | 
					 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
				
			||||||
 | 
					 * %%
 | 
				
			||||||
 | 
					 * This file is part of the Alfresco software.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * If the software was purchased under a paid Alfresco license, the terms of
 | 
				
			||||||
 | 
					 * the paid license agreement will prevail.  Otherwise, the software is
 | 
				
			||||||
 | 
					 * provided under the following open source license terms:
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * Alfresco is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU Lesser General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * Alfresco is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					 * GNU Lesser General Public License for more details.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU Lesser General Public License
 | 
				
			||||||
 | 
					 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 * #L%
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package org.alfresco.module.org_alfresco_module_rm.bulk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.service.ServiceRegistry;
 | 
				
			||||||
 | 
					import org.alfresco.service.cmr.repository.NodeRef;
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					import org.springframework.context.ApplicationEventPublisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public abstract class BulkBaseService<T> implements InitializingBean
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static final Log logger = LogFactory.getLog(BulkBaseService.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ServiceRegistry serviceRegistry;
 | 
				
			||||||
 | 
					    private SearchService searchService;
 | 
				
			||||||
 | 
					    private TransactionService transactionService;
 | 
				
			||||||
 | 
					    private SearchMapper searchMapper;
 | 
				
			||||||
 | 
					    private BulkMonitor<T> bulkMonitor;
 | 
				
			||||||
 | 
					    private ApplicationEventPublisher applicationEventPublisher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private int threadCount;
 | 
				
			||||||
 | 
					    private int batchSize;
 | 
				
			||||||
 | 
					    private long maxItems;
 | 
				
			||||||
 | 
					    private int loggingIntervalMs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void afterPropertiesSet() throws Exception
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.searchService = serviceRegistry.getSearchService();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public T execute(NodeRef holdRef, BulkOperation bulkOperation)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        checkPermissions(holdRef, bulkOperation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        long totalItems = getTotalItems(bulkOperation.searchQuery());
 | 
				
			||||||
 | 
					        if (maxItems < totalItems)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new RuntimeException("Too many items to process. Please refine your query.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String processId = UUID.randomUUID().toString();
 | 
				
			||||||
 | 
					        T initBulkStatus = getInitBulkStatus(processId, totalItems);
 | 
				
			||||||
 | 
					        bulkMonitor.updateBulkStatus(initBulkStatus);
 | 
				
			||||||
 | 
					        bulkMonitor.registerProcess(holdRef, processId);
 | 
				
			||||||
 | 
					        BatchProcessWorker<NodeRef> batchProcessWorker = getWorkerProvider(holdRef, bulkOperation);
 | 
				
			||||||
 | 
					        BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<NodeRef>(
 | 
				
			||||||
 | 
					            processId,
 | 
				
			||||||
 | 
					            transactionService.getRetryingTransactionHelper(),
 | 
				
			||||||
 | 
					            getWorkProvider(bulkOperation, totalItems),
 | 
				
			||||||
 | 
					            threadCount,
 | 
				
			||||||
 | 
					            batchSize,
 | 
				
			||||||
 | 
					            applicationEventPublisher,
 | 
				
			||||||
 | 
					            logger,
 | 
				
			||||||
 | 
					            loggingIntervalMs);
 | 
				
			||||||
 | 
					        ExecutorService executor = Executors.newSingleThreadExecutor();
 | 
				
			||||||
 | 
					        executor.submit(runBatchProcessor(batchProcessor, batchProcessWorker));
 | 
				
			||||||
 | 
					        return initBulkStatus;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected Callable<Void> runBatchProcessor(BatchProcessor<NodeRef> batchProcessor,
 | 
				
			||||||
 | 
					        BatchProcessWorker<NodeRef> batchProcessWorker)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return () -> {
 | 
				
			||||||
 | 
					            TaskScheduler taskScheduler = getTaskScheduler(batchProcessor, bulkMonitor);
 | 
				
			||||||
 | 
					            taskScheduler.schedule(loggingIntervalMs);
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                batchProcessor.processLong(batchProcessWorker, true);
 | 
				
			||||||
 | 
					                taskScheduler.runTask();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Throwable t)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                //TODO: handle exception
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            finally
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                taskScheduler.stopListening();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract T getInitBulkStatus(String processId, long totalItems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract TaskScheduler getTaskScheduler(BatchProcessor<NodeRef> batchProcessor, BulkMonitor<T> monitor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation, long totalItems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected abstract void checkPermissions(NodeRef holdRef, BulkOperation bulkOperation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected long getTotalItems(Query searchQuery)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        SearchParameters searchParams = new SearchParameters();
 | 
				
			||||||
 | 
					        searchMapper.setDefaults(searchParams);
 | 
				
			||||||
 | 
					        searchMapper.fromQuery(searchParams, searchQuery);
 | 
				
			||||||
 | 
					        searchParams.setSkipCount(0);
 | 
				
			||||||
 | 
					        searchParams.setMaxItems(1);
 | 
				
			||||||
 | 
					        return searchService.query(searchParams).getNumberFound();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.applicationEventPublisher = applicationEventPublisher;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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(long maxItems)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.maxItems = maxItems;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setLoggingIntervalMs(int loggingIntervalMs)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.loggingIntervalMs = loggingIntervalMs;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SearchMapper getSearchMapper()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return searchMapper;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public int getBatchSize()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return batchSize;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SearchService getSearchService()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return searchService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.alfresco.service.cmr.repository.NodeRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface BulkMonitor<T>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void updateBulkStatus(T bulkStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void registerProcess(NodeRef nodeRef, String processId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    T getBulkStatus(String processName);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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.module.org_alfresco_module_rm.bulk;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.alfresco.rest.api.search.model.Query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public record BulkOperation(Query searchQuery, String operationType)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface TaskScheduler
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void schedule(long msInterval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void runTask();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void stopListening();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.Comparator;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.BulkMonitor;
 | 
				
			||||||
 | 
					import org.alfresco.repo.cache.SimpleCache;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
 | 
					import org.alfresco.service.cmr.repository.NodeRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class HoldBulkMonitor implements BulkMonitor<HoldBulkStatus>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private SimpleCache<String, HoldBulkStatus> holdProgressCache;
 | 
				
			||||||
 | 
					    private SimpleCache<String, List<String>> holdProcessRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void updateBulkStatus(HoldBulkStatus holdBulkStatus)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        holdProgressCache.put(holdBulkStatus.processId(), holdBulkStatus);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void registerProcess(NodeRef holdRef, String processId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        List<String> processIds = Optional.ofNullable(holdProcessRegistry.get(holdRef.getId()))
 | 
				
			||||||
 | 
					            .orElse(new ArrayList<>());
 | 
				
			||||||
 | 
					        processIds.add(processId);
 | 
				
			||||||
 | 
					        holdProcessRegistry.put(holdRef.getId(), processIds);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public HoldBulkStatus getBulkStatus(String processName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return holdProgressCache.get(processName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<HoldBulkStatus> getBatchStatusesForHold(String holdId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Optional.ofNullable(holdProcessRegistry.get(holdId))
 | 
				
			||||||
 | 
					            .map(list -> list.stream()
 | 
				
			||||||
 | 
					                .map(this::getBulkStatus)
 | 
				
			||||||
 | 
					                .filter(Objects::nonNull)
 | 
				
			||||||
 | 
					                .sorted(Comparator.comparing(HoldBulkStatus::endTime, Comparator.nullsLast(Comparator.naturalOrder()))
 | 
				
			||||||
 | 
					                    .thenComparing(HoldBulkStatus::startTime, Comparator.nullsLast(Comparator.naturalOrder()))
 | 
				
			||||||
 | 
					                    .reversed())
 | 
				
			||||||
 | 
					                .toList())
 | 
				
			||||||
 | 
					            .orElse(Collections.emptyList());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setHoldProgressCache(
 | 
				
			||||||
 | 
					        SimpleCache<String, HoldBulkStatus> holdProgressCache)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.holdProgressCache = holdProgressCache;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setHoldProcessRegistry(
 | 
				
			||||||
 | 
					        SimpleCache<String, List<String>> holdProcessRegistry)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.holdProcessRegistry = holdProcessRegistry;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%L
 | 
				
			||||||
 | 
					 * Alfresco Records Management Module
 | 
				
			||||||
 | 
					 * %%
 | 
				
			||||||
 | 
					 * Copyright (C) 2005 - 2024 Alfresco Software Limited
 | 
				
			||||||
 | 
					 * %%
 | 
				
			||||||
 | 
					 * This file is part of the Alfresco software.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * If the software was purchased under a paid Alfresco license, the terms of
 | 
				
			||||||
 | 
					 * the paid license agreement will prevail.  Otherwise, the software is
 | 
				
			||||||
 | 
					 * provided under the following open source license terms:
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * Alfresco is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU Lesser General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * Alfresco is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					 * GNU Lesser General Public License for more details.
 | 
				
			||||||
 | 
					 * -
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU Lesser General Public License
 | 
				
			||||||
 | 
					 * along with Alfresco. If not, see <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.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
 | 
					import org.alfresco.service.cmr.repository.NodeRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface HoldBulkService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    HoldBulkStatus execute(NodeRef holdRef, BulkOperation bulkOperation);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,235 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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.concurrent.atomic.AtomicInteger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.alfresco.model.ContentModel;
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.BulkBaseService;
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.BulkMonitor;
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.BulkOperation;
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.TaskScheduler;
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					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.impl.SearchMapper;
 | 
				
			||||||
 | 
					import org.alfresco.rest.api.search.model.Query;
 | 
				
			||||||
 | 
					import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
 | 
					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.springframework.extensions.surf.util.I18NUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class HoldBulkServiceImpl extends BulkBaseService<HoldBulkStatus> implements HoldBulkService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private HoldService holdService;
 | 
				
			||||||
 | 
					    private static final String MSG_ERR_ACCESS_DENIED = "permissions.err_access_denied";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected TaskScheduler getTaskScheduler(BatchProcessor<NodeRef> batchProcessor,
 | 
				
			||||||
 | 
					        BulkMonitor<HoldBulkStatus> monitor)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new HoldTaskScheduler(() -> monitor.updateBulkStatus(
 | 
				
			||||||
 | 
					            new HoldBulkStatus(batchProcessor.getProcessName(), batchProcessor.getStartTime(),
 | 
				
			||||||
 | 
					                batchProcessor.getEndTime(), batchProcessor.getSuccessfullyProcessedEntriesLong(),
 | 
				
			||||||
 | 
					                batchProcessor.getTotalErrorsLong(), batchProcessor.getTotalResultsLong(),
 | 
				
			||||||
 | 
					                batchProcessor.getLastError())));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected BatchProcessWorkProvider<NodeRef> getWorkProvider(BulkOperation bulkOperation, long totalItems)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new AddToHoldWorkerProvider(new AtomicInteger(0), bulkOperation, totalItems);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected BatchProcessWorker<NodeRef> getWorkerProvider(NodeRef nodeRef, BulkOperation bulkOperation)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (ADD.name().equals(bulkOperation.operationType()))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new AddToHoldWorkerBatch(nodeRef);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new IllegalArgumentException("Unsupported action type when starting the bulk process: " + 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()))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class AddToHoldWorkerBatch implements BatchProcessWorker<NodeRef>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private final NodeRef holdRef;
 | 
				
			||||||
 | 
					        private final String currentUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public AddToHoldWorkerBatch(NodeRef holdRef)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.holdRef = holdRef;
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
 | 
				
			||||||
 | 
					            holdService.addToHold(holdRef, entry);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void afterProcess()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            AuthenticationUtil.popAuthentication();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class AddToHoldWorkerProvider implements BatchProcessWorkProvider<NodeRef>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private final AtomicInteger currentNodeNumber;
 | 
				
			||||||
 | 
					        private final Query searchQuery;
 | 
				
			||||||
 | 
					        private final String currentUser;
 | 
				
			||||||
 | 
					        private final long totalItems;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public AddToHoldWorkerProvider(AtomicInteger currentNodeNumber, BulkOperation bulkOperation, long totalItems)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.currentNodeNumber = currentNodeNumber;
 | 
				
			||||||
 | 
					            this.searchQuery = bulkOperation.searchQuery();
 | 
				
			||||||
 | 
					            this.totalItems = totalItems;
 | 
				
			||||||
 | 
					            currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public int getTotalEstimatedWorkSize()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return (int) totalItems;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public long getTotalEstimatedWorkSizeLong()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return totalItems;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public Collection<NodeRef> getNextWork()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            AuthenticationUtil.pushAuthentication();
 | 
				
			||||||
 | 
					            AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
 | 
				
			||||||
 | 
					            SearchParameters searchParams = getNextPageParameters();
 | 
				
			||||||
 | 
					            ResultSet result = getSearchService().query(searchParams);
 | 
				
			||||||
 | 
					            if (result.getNodeRefs().isEmpty())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Collections.emptyList();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            AuthenticationUtil.popAuthentication();
 | 
				
			||||||
 | 
					            currentNodeNumber.addAndGet(getBatchSize());
 | 
				
			||||||
 | 
					            return result.getNodeRefs();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private SearchParameters getNextPageParameters()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SearchParameters searchParams = new SearchParameters();
 | 
				
			||||||
 | 
					            SearchMapper searchMapper = getSearchMapper();
 | 
				
			||||||
 | 
					            searchMapper.setDefaults(searchParams);
 | 
				
			||||||
 | 
					            searchMapper.fromQuery(searchParams, searchQuery);
 | 
				
			||||||
 | 
					            searchParams.setSkipCount(currentNodeNumber.get());
 | 
				
			||||||
 | 
					            searchParams.setMaxItems(getBatchSize());
 | 
				
			||||||
 | 
					            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,63 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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.concurrent.Executors;
 | 
				
			||||||
 | 
					import java.util.concurrent.ScheduledExecutorService;
 | 
				
			||||||
 | 
					import java.util.concurrent.ScheduledFuture;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.bulk.TaskScheduler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class HoldTaskScheduler implements TaskScheduler
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
 | 
				
			||||||
 | 
					    private ScheduledFuture<?> scheduledTask;
 | 
				
			||||||
 | 
					    private Runnable task;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public HoldTaskScheduler(Runnable task)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.task = task;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void schedule(long loggingInterval)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        scheduledTask = executor.scheduleAtFixedRate(task, loggingInterval, loggingInterval, TimeUnit.MILLISECONDS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void runTask()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        task.run();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void stopListening()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        scheduledTask.cancel(false);
 | 
				
			||||||
 | 
					        executor.shutdown();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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 org.alfresco.module.org_alfresco_module_rm.bulk.hold.HoldBulkMonitor;
 | 
				
			||||||
 | 
					import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
				
			||||||
 | 
					import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
 | 
				
			||||||
 | 
					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.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
 | 
					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<HoldBulkStatus>, RelationshipResourceAction.ReadById<HoldBulkStatus>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private HoldBulkMonitor holdBulkMonitor;
 | 
				
			||||||
 | 
					    private FilePlanComponentsApiUtils apiUtils;
 | 
				
			||||||
 | 
					    private PermissionService permissionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public CollectionWithPagingInfo<HoldBulkStatus> readAll(String holdId, Parameters parameters)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // validate parameters
 | 
				
			||||||
 | 
					        checkNotBlank("holdId", holdId);
 | 
				
			||||||
 | 
					        mandatory("parameters", parameters);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        checkReadPermissions(holdRef);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<HoldBulkStatus> statuses = holdBulkMonitor.getBatchStatusesForHold(holdId);
 | 
				
			||||||
 | 
					        List<HoldBulkStatus> page = statuses.stream()
 | 
				
			||||||
 | 
					            .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 HoldBulkStatus readById(String holdId, String processId, Parameters parameters)
 | 
				
			||||||
 | 
					        throws RelationshipResourceNotFoundException
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        checkNotBlank("processId", processId);
 | 
				
			||||||
 | 
					        mandatory("parameters", parameters);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        NodeRef holdRef = apiUtils.lookupAndValidateNodeType(holdId, RecordsManagementModel.TYPE_HOLD);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        checkReadPermissions(holdRef);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Optional.ofNullable(holdBulkMonitor.getBulkStatus(processId)).orElseThrow(() -> new EntityNotFoundException(processId));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,6 +30,8 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c
 | 
				
			|||||||
import static org.alfresco.util.ParameterCheck.mandatory;
 | 
					import static org.alfresco.util.ParameterCheck.mandatory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					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.hold.HoldService;
 | 
				
			||||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
					import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
 | 
				
			||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
					import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | 
				
			||||||
@@ -42,6 +44,8 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
 | 
				
			|||||||
import org.alfresco.rest.framework.webscripts.WithResponse;
 | 
					import org.alfresco.rest.framework.webscripts.WithResponse;
 | 
				
			||||||
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
 | 
					import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
 | 
				
			||||||
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
					import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkOperation;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
import org.alfresco.rm.rest.api.model.HoldDeletionReason;
 | 
					import org.alfresco.rm.rest.api.model.HoldDeletionReason;
 | 
				
			||||||
import org.alfresco.rm.rest.api.model.HoldModel;
 | 
					import org.alfresco.rm.rest.api.model.HoldModel;
 | 
				
			||||||
import org.alfresco.service.cmr.model.FileFolderService;
 | 
					import org.alfresco.service.cmr.model.FileFolderService;
 | 
				
			||||||
@@ -68,6 +72,7 @@ public class HoldsEntityResource implements
 | 
				
			|||||||
    private ApiNodesModelFactory nodesModelFactory;
 | 
					    private ApiNodesModelFactory nodesModelFactory;
 | 
				
			||||||
    private HoldService holdService;
 | 
					    private HoldService holdService;
 | 
				
			||||||
    private TransactionService transactionService;
 | 
					    private TransactionService transactionService;
 | 
				
			||||||
 | 
					    private HoldBulkService holdBulkService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void afterPropertiesSet() throws Exception
 | 
					    public void afterPropertiesSet() throws Exception
 | 
				
			||||||
@@ -157,6 +162,22 @@ public class HoldsEntityResource implements
 | 
				
			|||||||
        return reason;
 | 
					        return reason;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Operation("bulk")
 | 
				
			||||||
 | 
					    @WebApiDescription(title = "Start the hold bulk operation",
 | 
				
			||||||
 | 
					        successStatus = HttpServletResponse.SC_ACCEPTED)
 | 
				
			||||||
 | 
					    public HoldBulkStatus 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return holdBulkService.execute(parentNodeRef,
 | 
				
			||||||
 | 
					            new BulkOperation(holdBulkOperation.query(), holdBulkOperation.op().name()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
					    public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        this.apiUtils = apiUtils;
 | 
					        this.apiUtils = apiUtils;
 | 
				
			||||||
@@ -181,4 +202,9 @@ public class HoldsEntityResource implements
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        this.transactionService = transactionService;
 | 
					        this.transactionService = transactionService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setHoldBulkService(HoldBulkService holdBulkService)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.holdBulkService = holdBulkService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,32 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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 enum HoldBulkOperationType
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ADD
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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.io.Serializable;
 | 
				
			||||||
 | 
					import java.text.NumberFormat;
 | 
				
			||||||
 | 
					import java.util.Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public record HoldBulkStatus(String processId, Date startTime, Date endTime, long itemsProcessed, long errorsCount,
 | 
				
			||||||
 | 
					                             long totalItems, String lastError) implements Serializable
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public enum Status
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        PENDING("PENDING"),
 | 
				
			||||||
 | 
					        IN_PROGRESS("IN PROGRESS"),
 | 
				
			||||||
 | 
					        DONE("DONE");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private final String value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Status(String value)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.value = value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public String getValue()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getStatus()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String getPercentageProcessed()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return itemsProcessed <= totalItems ? NumberFormat.getPercentInstance().format(
 | 
				
			||||||
 | 
					            totalItems == 0 ? 1.0F : (float) itemsProcessed / totalItems) : "Unknown";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * #%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.repo.cache.SimpleCache;
 | 
				
			||||||
 | 
					import org.alfresco.rm.rest.api.model.HoldBulkStatus;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.mockito.Mock;
 | 
				
			||||||
 | 
					import org.mockito.MockitoAnnotations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.Date;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class HoldBulkMonitorTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Mock
 | 
				
			||||||
 | 
					    private SimpleCache<String, HoldBulkStatus> holdProgressCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Mock
 | 
				
			||||||
 | 
					    private SimpleCache<String, List<String>> holdProcessRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private HoldBulkMonitor holdBulkMonitor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Before
 | 
				
			||||||
 | 
					    public void setUp() {
 | 
				
			||||||
 | 
					        MockitoAnnotations.openMocks(this);
 | 
				
			||||||
 | 
					        holdBulkMonitor = new HoldBulkMonitor();
 | 
				
			||||||
 | 
					        holdBulkMonitor.setHoldProgressCache(holdProgressCache);
 | 
				
			||||||
 | 
					        holdBulkMonitor.setHoldProcessRegistry(holdProcessRegistry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void getBatchStatusesForHoldReturnsEmptyListWhenNoProcesses() {
 | 
				
			||||||
 | 
					        when(holdProcessRegistry.get("holdId")).thenReturn(null);
 | 
				
			||||||
 | 
					        assertEquals(Collections.emptyList(), holdBulkMonitor.getBatchStatusesForHold("holdId"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void getBatchStatusesForHoldReturnsSortedStatuses() {
 | 
				
			||||||
 | 
					        HoldBulkStatus status1 = new HoldBulkStatus(null, new Date(1000), new Date(2000), 0L, 0L, 0L, null);
 | 
				
			||||||
 | 
					        HoldBulkStatus status2 = new HoldBulkStatus(null, new Date(3000), null, 0L, 0L, 0L, null);
 | 
				
			||||||
 | 
					        HoldBulkStatus status3 = new HoldBulkStatus(null, new Date(4000), null, 0L, 0L, 0L, null);
 | 
				
			||||||
 | 
					        HoldBulkStatus status4 = new HoldBulkStatus(null, new Date(500), new Date(800), 0L, 0L, 0L, null);
 | 
				
			||||||
 | 
					        HoldBulkStatus status5 = new HoldBulkStatus(null, null, null, 0L, 0L, 0L, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        when(holdProcessRegistry.get("holdId")).thenReturn(Arrays.asList("process1", "process2", "process3", "process4", "process5"));
 | 
				
			||||||
 | 
					        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), holdBulkMonitor.getBatchStatusesForHold("holdId"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2314,6 +2314,112 @@ paths:
 | 
				
			|||||||
          description: Unexpected error
 | 
					          description: Unexpected error
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/Error'
 | 
					            $ref: '#/definitions/Error'
 | 
				
			||||||
 | 
					  '/holds/{holdId}/bulk-statuses':
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - holds
 | 
				
			||||||
 | 
					      operationId: bulkStatuses
 | 
				
			||||||
 | 
					      summary: Get bulk statuses
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Gets bulk statuses for hold with id **holdId**.
 | 
				
			||||||
 | 
					      parameters:
 | 
				
			||||||
 | 
					        - $ref: '#/parameters/holdIdParam'
 | 
				
			||||||
 | 
					        - $ref: '#/parameters/skipCountParam'
 | 
				
			||||||
 | 
					        - $ref: '#/parameters/maxItemsParam'
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '202':
 | 
				
			||||||
 | 
					          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/{processId}':
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - holds
 | 
				
			||||||
 | 
					      operationId: bulkStatus
 | 
				
			||||||
 | 
					      summary: Get the bulk status
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Gets the bulk status specified by **processId** for **holdId**.
 | 
				
			||||||
 | 
					      parameters:
 | 
				
			||||||
 | 
					        - $ref: '#/parameters/holdIdParam'
 | 
				
			||||||
 | 
					        - $ref: '#/parameters/processId'
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '202':
 | 
				
			||||||
 | 
					          description: Successful response
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/HoldBulkStatus'
 | 
				
			||||||
 | 
					        '400':
 | 
				
			||||||
 | 
					          description: |
 | 
				
			||||||
 | 
					            Invalid parameter: **holdId** or **processId** is not a valid format
 | 
				
			||||||
 | 
					        '401':
 | 
				
			||||||
 | 
					          description: Authentication failed
 | 
				
			||||||
 | 
					        '403':
 | 
				
			||||||
 | 
					          description: Current user does not have permission to read **holdId**
 | 
				
			||||||
 | 
					        '404':
 | 
				
			||||||
 | 
					          description: "**holdId** or **processId** does not exist"
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          description: Unexpected error
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/Error'
 | 
				
			||||||
 | 
					  '/holds/{holdId}/bulk':
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      tags:
 | 
				
			||||||
 | 
					        - holds
 | 
				
			||||||
 | 
					      operationId: holdBulk
 | 
				
			||||||
 | 
					      summary: Start the hold bulk process
 | 
				
			||||||
 | 
					      description: |
 | 
				
			||||||
 | 
					        Start the asynchronous bulk process of a hold with id **holdId** based on search query results.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ```JSON
 | 
				
			||||||
 | 
					        For example, the following JSON body start 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/HoldBulkStatus'
 | 
				
			||||||
 | 
					        '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 of **holdId**
 | 
				
			||||||
 | 
					        '404':
 | 
				
			||||||
 | 
					          description: "**holdId** does not exist"
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          description: Unexpected error
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/Error'
 | 
				
			||||||
  '/holds/{holdId}/delete':
 | 
					  '/holds/{holdId}/delete':
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
@@ -2862,6 +2968,12 @@ parameters:
 | 
				
			|||||||
    description: The identifier of a child of a hold.
 | 
					    description: The identifier of a child of a hold.
 | 
				
			||||||
    required: true
 | 
					    required: true
 | 
				
			||||||
    type: string
 | 
					    type: string
 | 
				
			||||||
 | 
					  processId:
 | 
				
			||||||
 | 
					    name: processId
 | 
				
			||||||
 | 
					    in: path
 | 
				
			||||||
 | 
					    description: The identifier of a bulk process.
 | 
				
			||||||
 | 
					    required: true
 | 
				
			||||||
 | 
					    type: string
 | 
				
			||||||
  ## Record
 | 
					  ## Record
 | 
				
			||||||
  recordIdParam:
 | 
					  recordIdParam:
 | 
				
			||||||
    name: recordId
 | 
					    name: recordId
 | 
				
			||||||
@@ -4018,6 +4130,84 @@ definitions:
 | 
				
			|||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
      reason:
 | 
					      reason:
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
 | 
					  RequestQuery:
 | 
				
			||||||
 | 
					    description: Query.
 | 
				
			||||||
 | 
					    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 exact 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/RequestQuery'
 | 
				
			||||||
 | 
					      op:
 | 
				
			||||||
 | 
					        description: The query language in which the query is written.
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        default: ADD
 | 
				
			||||||
 | 
					        enum:
 | 
				
			||||||
 | 
					          - ADD
 | 
				
			||||||
 | 
					  HoldBulkStatus:
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      processId:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      startTime:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        format: date-time
 | 
				
			||||||
 | 
					      endTime:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					        format: date-time
 | 
				
			||||||
 | 
					      itemsProcessed:
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					  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:
 | 
					  RequestBodyFile:
 | 
				
			||||||
    type: object
 | 
					    type: object
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -709,4 +709,6 @@ cache.ldapInitialDirContextCache.cluster.type=fully-distributed
 | 
				
			|||||||
cache.ldapInitialDirContextCache.backup-count=1
 | 
					cache.ldapInitialDirContextCache.backup-count=1
 | 
				
			||||||
cache.ldapInitialDirContextCache.eviction-policy=NONE
 | 
					cache.ldapInitialDirContextCache.eviction-policy=NONE
 | 
				
			||||||
cache.ldapInitialDirContextCache.merge-policy=com.hazelcast.spi.merge.LatestUpdateMergePolicy
 | 
					cache.ldapInitialDirContextCache.merge-policy=com.hazelcast.spi.merge.LatestUpdateMergePolicy
 | 
				
			||||||
cache.ldapInitialDirContextCache.readBackupData=false
 | 
					cache.ldapInitialDirContextCache.readBackupData=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cache.workerRegistryCache.type=fully-distributed
 | 
				
			||||||
		Reference in New Issue
	
	Block a user