BatchService.java
/*
* Copyright (c) 2002-2024, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.plugins.identityimport.service;
import fr.paris.lutece.api.user.User;
import fr.paris.lutece.plugins.identityimport.business.Batch;
import fr.paris.lutece.plugins.identityimport.business.BatchHome;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentity;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityAttribute;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityAttributeHome;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHistory;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHistoryHome;
import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHome;
import fr.paris.lutece.plugins.identityimport.business.ResourceState;
import fr.paris.lutece.plugins.identityimport.wf.WorkflowBean;
import fr.paris.lutece.plugins.identityimport.wf.WorkflowBeanService;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.BatchDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchResourceStateDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatisticsDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatusDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatusMode;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatusResponse;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.ImportingHistoryDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.ResponseStatusFactory;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceHistory;
import fr.paris.lutece.portal.service.progressmanager.ProgressManagerService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.sql.TransactionManager;
import org.apache.commons.lang3.StringUtils;
import java.sql.Date;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class BatchService
{
private static final String BATCH_WFBEANSERVICE = "identityimport.batch.wfbeanservice";
private static final String CANDIDATEIDENTITY_WFBEANSERVICE = "identityimport.candidateidentity.wfbeanservice";
private static final int ARCHIVE_ACTION_ID = AppPropertiesService.getPropertyInt( "identityimport.archive.action.id", 0 );
private static final int VALIDATE_BATCH_ACTION_ID = AppPropertiesService.getPropertyInt( "identityimport.validate.action.id", 0 );
private final WorkflowBeanService<CandidateIdentity> _wfIdentityBeanService = SpringContextService.getBean( CANDIDATEIDENTITY_WFBEANSERVICE );
private final WorkflowBeanService<Batch> _wfBatchBeanService = SpringContextService.getBean( BATCH_WFBEANSERVICE );
private final ProgressManagerService progressManagerService = ProgressManagerService.getInstance( );
private final BatchValidationService validationService = BatchValidationService.instance( );
private static BatchService instance;
public static BatchService instance( )
{
if ( instance == null )
{
instance = new BatchService( );
}
return instance;
}
public int importBatch( final BatchDto batch, final User user, final String feedToken ) throws IdentityStoreException
{
// Ensure that provided batch size does not exceed the limit defined in properties
validationService.validateImportBatchLimit( batch );
TransactionManager.beginTransaction( null );
try
{
// Validate the batch definition and compliance to client service contract
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.initFeed( feedToken, batch.getIdentities( ).size( ) );
progressManagerService.addReport( feedToken, "Validating batch ..." );
}
validationService.validateBatch( batch );
// Try to retrieve the batch by its reference, if exists ensure that both side information is consistent, if not, create it.
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.addReport( feedToken, "Creating batch..." );
}
if ( BatchHome.getBatch( batch.getReference( ) ) != null )
{
throw new IdentityStoreException( "A batch already exists with this reference." );
}
final Batch bean = this.getBean( batch );
bean.setDate( new Date( System.currentTimeMillis( ) ) );
BatchHome.create( bean );
// Init workflow resource
final int batchId = bean.getId( );
final WorkflowBean<Batch> batchWorkflowBean = _wfBatchBeanService.createWorkflowBean( bean, batchId, user );
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.addReport( feedToken, "Batch created with id " + batchId );
}
// Import identities
final String appCode = bean.getAppCode( );
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.addReport( feedToken, "Creating candidate identities..." );
}
for ( final IdentityDto identity : batch.getIdentities( ) )
{
final CandidateIdentity candidateIdentity = new CandidateIdentity( );
candidateIdentity.setIdBatch( batchId );
candidateIdentity.setExternalCustomerId( identity.getExternalCustomerId( ) );
candidateIdentity.setConnectionId( identity.getConnectionId( ) );
candidateIdentity.setClientAppCode( appCode );
CandidateIdentityHome.create( candidateIdentity );
_wfIdentityBeanService.createWorkflowBean( candidateIdentity, candidateIdentity.getId( ), candidateIdentity.getIdBatch( ), user );
for ( final AttributeDto importedAttribute : identity.getAttributes( ) )
{
final CandidateIdentityAttribute candidateAttribute = new CandidateIdentityAttribute( );
candidateAttribute.setIdIdentity( candidateIdentity.getId( ) );
candidateAttribute.setCode( importedAttribute.getKey( ) );
candidateAttribute.setValue( importedAttribute.getValue( ) );
candidateAttribute.setCertDate( new Date( importedAttribute.getCertificationDate( ).getTime( ) ) );
candidateAttribute.setCertProcess( importedAttribute.getCertifier( ) );
CandidateIdentityAttributeHome.create( candidateAttribute );
}
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.incrementSuccess( feedToken, 1 );
}
}
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.addReport( feedToken, "Created" + batch.getIdentities( ).size( ) + " candidate identities" );
}
TransactionManager.commitTransaction( null );
// _wfBatchBeanService.processActionNoUser( batchWorkflowBean, VALIDATE_BATCH_ACTION_ID, null, Locale.getDefault( ) );
return bean.getId( );
}
catch( final Exception e )
{
TransactionManager.rollBack( null );
if ( StringUtils.isNotEmpty( feedToken ) )
{
progressManagerService.addReport( feedToken, "An error occurred during creation..." );
}
throw new IdentityStoreException( e.getMessage( ), e );
}
}
/**
* Find expired batches (limited to batchLimit param) and purge them. Deletion of {@link CandidateIdentity}, {@link CandidateIdentityAttribute},
* {@link CandidateIdentityHistory}
*
* @param batchLimit
* @return
*/
public StringBuilder purgeBatches( final int batchLimit ) throws IdentityStoreException
{
final StringBuilder msg = new StringBuilder( );
final List<Batch> expiredBatches = BatchHome.findExpiredBatches( batchLimit );
msg.append( expiredBatches.size( ) ).append( " expired batches found" ).append( System.lineSeparator( ) );
if ( ARCHIVE_ACTION_ID > 0 )
{
for ( final Batch expiredBatch : expiredBatches )
{
msg.append( "Removing expired batch : " ).append( expiredBatch.toLog( ) ).append( System.lineSeparator( ) );
final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( expiredBatch, expiredBatch.getId( ), null );
_wfBatchBeanService.processAutomaticAction( workflowBean, ARCHIVE_ACTION_ID, null, Locale.getDefault( ) );
}
}
else
{
AppLogService.error( "Property identityimport.archive.action.id must be defined in order to perform archiving task." );
}
// return message for daemons
return msg;
}
/**
* Purge given batch. Deletion of {@link CandidateIdentity}, {@link CandidateIdentityAttribute}, {@link CandidateIdentityHistory}
*
* @param batchId
* the id of the batch
* @return
*/
public void purgeBatch( final int batchId ) throws IdentityStoreException
{
final Optional<Batch> expiredBatch = BatchHome.findByPrimaryKey( batchId );
if ( expiredBatch.isPresent( ) )
{
final Batch batch = expiredBatch.get( );
TransactionManager.beginTransaction( null );
try
{
final List<Integer> idCandidateIdentitiesList = CandidateIdentityHome.getIdCandidateIdentitiesList( batch.getId( ) );
CandidateIdentityHome.delete( idCandidateIdentitiesList );
CandidateIdentityAttributeHome.delete( idCandidateIdentitiesList );
CandidateIdentityHistoryHome.delete( idCandidateIdentitiesList );
TransactionManager.commitTransaction( null );
}
catch( final Exception e )
{
TransactionManager.rollBack( null );
throw new IdentityStoreException( "An error occurred during batch purge.", e );
}
}
else
{
throw new IdentityStoreException( "Could not find batch with ID " + batchId );
}
}
/**
* Find newly created batches (limited to batchLimit param) and launch them automatically.
*
* @param batchLimit
* the limit
* @return logs
*/
public StringBuilder launchBatches( final int batchLimit )
{
final StringBuilder msg = new StringBuilder( );
try
{
final List<Batch> initialStateBatches = BatchHome.findInitialStateBatches( batchLimit );
if ( !initialStateBatches.isEmpty( ) )
{
msg.append( initialStateBatches.size( ) ).append( " initial state batches found" ).append( System.lineSeparator( ) );
final int actionId = BatchHome.getBatchInitialActionId( );
for ( final Batch batch : initialStateBatches )
{
msg.append( "Launching batch : " ).append( batch.toLog( ) ).append( System.lineSeparator( ) );
final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( batch, batch.getId( ), null );
_wfBatchBeanService.processAutomaticAction( workflowBean, actionId, null, Locale.getDefault( ) );
}
}
else
{
msg.append( "No initial state batches found." ).append( System.lineSeparator( ) );
}
}
catch( final Exception e )
{
msg.append( "Error occurred while launching batches automatically :: " ).append( System.lineSeparator( ) ).append( e.getMessage( ) )
.append( System.lineSeparator( ) );
}
return msg;
}
/**
* Find batches that can be closed, and close them
*
* @param batchLimit
* the limit
* @return logs
*/
public StringBuilder closeBatches( int batchLimit )
{
final StringBuilder msg = new StringBuilder( );
try
{
final List<Batch> closableBatches = BatchHome.findClosableBatches( batchLimit );
if ( !closableBatches.isEmpty( ) )
{
msg.append( closableBatches.size( ) ).append( " in treatment state batches found" ).append( System.lineSeparator( ) );
final int actionId = BatchHome.getBatchInTreatmentActionId( );
for ( final Batch batch : closableBatches )
{
msg.append( "Closing batch : " ).append( batch.toLog( ) ).append( System.lineSeparator( ) );
final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( batch, batch.getId( ), null );
_wfBatchBeanService.processAutomaticAction( workflowBean, actionId, null, Locale.getDefault( ) );
}
}
else
{
msg.append( "No in treatment state batches found." ).append( System.lineSeparator( ) );
}
}
catch( final Exception e )
{
msg.append( "Error occurred while closing batches automatically :: " ).append( System.lineSeparator( ) ).append( e.getMessage( ) )
.append( System.lineSeparator( ) );
}
return msg;
}
public Batch getBean( BatchDto batch )
{
final Batch bean = new Batch( );
bean.setReference( batch.getReference( ) );
bean.setComment( batch.getComment( ) );
bean.setDate( batch.getDate( ) );
bean.setUser( batch.getUser( ) );
bean.setAppCode( batch.getAppCode( ) );
return bean;
}
public BatchDto getDto( Batch batch )
{
final BatchDto dto = new BatchDto( );
dto.setReference( batch.getReference( ) );
dto.setComment( batch.getComment( ) );
dto.setDate( batch.getDate( ) );
dto.setUser( batch.getUser( ) );
dto.setAppCode( batch.getAppCode( ) );
return dto;
}
public BatchStatusResponse getBatchStatus( final String strBatchReference, final BatchStatusMode mode ) throws IdentityStoreException
{
final BatchStatusResponse response = new BatchStatusResponse( );
final Batch batch = BatchHome.getBatch( strBatchReference );
if ( batch == null )
{
response.setStatus(
ResponseStatusFactory.notFound( ).setMessageKey( Constants.PROPERTY_REST_ERROR_BATCH_NOT_FOUND ).setMessage( "Batch not found." ) );
return response;
}
final ResourceState batchState = BatchHome.getBatchState( batch.getId( ) );
if ( batchState == null )
{
response.setStatus( ResponseStatusFactory.notFound( ).setMessageKey( Constants.PROPERTY_REST_ERROR_BATCH_STATE_NOT_FOUND )
.setMessage( "State of batch not found." ) );
return response;
}
final BatchStatusDto batchStatus = new BatchStatusDto( );
batchStatus.setReference( batch.getReference( ) );
batchStatus.setClientCode( batch.getAppCode( ) );
batchStatus.setUser( batch.getUser( ) );
batchStatus.setComment( batch.getComment( ) );
batchStatus.setCreationDate( batch.getDate( ) );
batchStatus.setStatus( batchState.getName( ) );
batchStatus.setStatusDescription( batchState.getDescription( ) );
final BatchStatisticsDto statisticsDto = new BatchStatisticsDto( );
batchStatus.setStatistics( statisticsDto );
final List<ResourceState> candidateIdentityStates = CandidateIdentityHome.getCandidateIdentityStates( batch.getId( ) );
candidateIdentityStates.stream( ).filter( r -> r.getResourceCount( ) > 0 ).forEach( resourceState -> {
final BatchResourceStateDto resourceStateDto = new BatchResourceStateDto( );
statisticsDto.getResourceStates( ).add( resourceStateDto );
resourceStateDto.setName( resourceState.getName( ) );
resourceStateDto.setDescription( resourceState.getDescription( ) );
resourceStateDto.setInitialState( resourceStateDto.isInitialState( ) );
resourceStateDto.setResourceCount( resourceState.getResourceCount( ) );
} );
statisticsDto.setTotalResourceCount( candidateIdentityStates.stream( ).mapToInt( ResourceState::getResourceCount ).sum( ) );
if ( mode != BatchStatusMode.IDENTITIES_ONLY )
{
final List<ResourceHistory> batchHistory = BatchHome.getBatchHistory( batch.getId( ) );
for ( final ResourceHistory history : batchHistory )
{
final ImportingHistoryDto historyDto = new ImportingHistoryDto( );
historyDto.setActionName( history.getAction( ).getName( ) );
historyDto.setActionDescription( history.getAction( ).getDescription( ) );
historyDto.setDate( history.getCreationDate( ) );
historyDto.setUserAccessCode( history.getUserAccessCode( ) );
batchStatus.getBatchHistory( ).add( historyDto );
}
}
if ( mode != BatchStatusMode.BATCH_ONLY )
{
final List<CandidateIdentity> identities = CandidateIdentityHome
.getCandidateIdentitiesListByIds( CandidateIdentityHome.getIdCandidateIdentitiesList( batch.getId( ) ) );
identities.forEach( candidate -> {
final List<CandidateIdentityHistory> identityHistoryList = CandidateIdentityHistoryHome.selectAll( candidate.getId( ) );
final List<ResourceHistory> history = CandidateIdentityHome.getHistory( candidate.getId( ) );
batchStatus.getIdentities( ).add( CandidateIdentityService.instance( ).getDto( candidate, identityHistoryList, history ) );
} );
}
response.setBatchStatus( batchStatus );
response.setStatus( ResponseStatusFactory.ok( ).setMessageKey( Constants.PROPERTY_REST_INFO_SUCCESSFUL_OPERATION )
.setMessage( "Status du batch récupéré avec succès" ) );
return response;
}
}