View Javadoc
1   /*
2    * Copyright (c) 2002-2024, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.identityimport.service;
35  
36  import fr.paris.lutece.api.user.User;
37  import fr.paris.lutece.plugins.identityimport.business.Batch;
38  import fr.paris.lutece.plugins.identityimport.business.BatchHome;
39  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentity;
40  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityAttribute;
41  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityAttributeHome;
42  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHistory;
43  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHistoryHome;
44  import fr.paris.lutece.plugins.identityimport.business.CandidateIdentityHome;
45  import fr.paris.lutece.plugins.identityimport.business.ResourceState;
46  import fr.paris.lutece.plugins.identityimport.wf.WorkflowBean;
47  import fr.paris.lutece.plugins.identityimport.wf.WorkflowBeanService;
48  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
49  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.BatchDto;
50  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
51  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchImportRequest;
52  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchResourceStateDto;
53  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatisticsDto;
54  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatusDto;
55  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchStatusMode;
56  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.ImportingHistoryDto;
57  import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
58  import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
59  import fr.paris.lutece.plugins.identitystore.web.exception.ResourceNotFoundException;
60  import fr.paris.lutece.plugins.workflowcore.business.resource.ResourceHistory;
61  import fr.paris.lutece.portal.service.progressmanager.ProgressManagerService;
62  import fr.paris.lutece.portal.service.spring.SpringContextService;
63  import fr.paris.lutece.portal.service.util.AppLogService;
64  import fr.paris.lutece.portal.service.util.AppPropertiesService;
65  import fr.paris.lutece.util.sql.TransactionManager;
66  import org.apache.commons.lang3.StringUtils;
67  
68  import java.sql.Date;
69  import java.sql.Timestamp;
70  import java.time.Instant;
71  import java.util.List;
72  import java.util.Locale;
73  import java.util.Optional;
74  import java.util.UUID;
75  
76  public class BatchService
77  {
78      private static final String BATCH_WFBEANSERVICE = "identityimport.batch.wfbeanservice";
79      private static final String CANDIDATEIDENTITY_WFBEANSERVICE = "identityimport.candidateidentity.wfbeanservice";
80  
81      private static final int ARCHIVE_ACTION_ID = AppPropertiesService.getPropertyInt( "identityimport.archive.action.id", 0 );
82      private static final int VALIDATE_BATCH_ACTION_ID = AppPropertiesService.getPropertyInt( "identityimport.validate.action.id", 0 );
83  
84      private static final String MESSAGE_KEY_BATCH_EXISTS_WITH_SAME_REFERENCE = "identityimport.error.batch.exists.with.same.reference";
85      private static final String MESSAGE_KEY_BATCH_ERROR_DURING_CREATION = "identityimport.error.batch.during.creation";
86  
87      private final WorkflowBeanService<CandidateIdentity> _wfIdentityBeanService = SpringContextService.getBean( CANDIDATEIDENTITY_WFBEANSERVICE );
88      private final WorkflowBeanService<Batch> _wfBatchBeanService = SpringContextService.getBean( BATCH_WFBEANSERVICE );
89      private final ProgressManagerService progressManagerService = ProgressManagerService.getInstance( );
90      private final BatchValidationService validationService = BatchValidationService.instance( );
91  
92      private static BatchService instance;
93  
94      public static BatchService instance( )
95      {
96          if ( instance == null )
97          {
98              instance = new BatchService( );
99          }
100         return instance;
101     }
102 
103     public String importBatchFromApi(final BatchImportRequest request, final String clientCode) throws IdentityStoreException {
104         final BatchDto batch = request.getBatch();
105         batch.setClientCode(clientCode);
106         do {
107             batch.setReference(UUID.randomUUID().toString());
108         } while (BatchHome.getBatch(batch.getReference()) != null);
109 
110         TransactionManager.beginTransaction( null );
111         try
112         {
113             final Batch bean = this.getBean( batch );
114             bean.setCreationDate( Timestamp.from( Instant.now( ) ) );
115             BatchHome.create( bean );
116 
117             // Init workflow resource
118             final int batchId = bean.getId( );
119             final WorkflowBean<Batch> batchWorkflowBean = _wfBatchBeanService.createWorkflowBean( bean, batchId, null );
120 
121             // Import identities
122             for ( final IdentityDto identity : batch.getIdentities( ) )
123             {
124                 final CandidateIdentityess/CandidateIdentity.html#CandidateIdentity">CandidateIdentity candidateIdentity = new CandidateIdentity( );
125                 candidateIdentity.setIdBatch( batchId );
126                 candidateIdentity.setExternalCustomerId( identity.getExternalCustomerId( ) );
127                 candidateIdentity.setConnectionId( identity.getConnectionId( ) );
128                 candidateIdentity.setClientCode( bean.getClientCode( ) );
129                 CandidateIdentityHome.create( candidateIdentity );
130                 _wfIdentityBeanService.createWorkflowBean( candidateIdentity, candidateIdentity.getId( ), candidateIdentity.getIdBatch( ), null );
131 
132                 for ( final AttributeDto importedAttribute : identity.getAttributes( ) )
133                 {
134                     final CandidateIdentityAttributeateIdentityAttribute.html#CandidateIdentityAttribute">CandidateIdentityAttribute candidateAttribute = new CandidateIdentityAttribute( );
135                     candidateAttribute.setIdIdentity( candidateIdentity.getId( ) );
136                     candidateAttribute.setCode( importedAttribute.getKey( ) );
137                     candidateAttribute.setValue( importedAttribute.getValue( ) );
138                     candidateAttribute.setCertDate( Timestamp.from( importedAttribute.getCertificationDate( ).toInstant( ) ) );
139                     candidateAttribute.setCertProcess( importedAttribute.getCertifier( ) );
140                     CandidateIdentityAttributeHome.create( candidateAttribute );
141                 }
142             }
143             TransactionManager.commitTransaction( null );
144             _wfBatchBeanService.processActionNoUser( batchWorkflowBean, VALIDATE_BATCH_ACTION_ID, null, Locale.getDefault( ) );
145             return batch.getReference();
146         }
147         catch( final Exception e )
148         {
149             TransactionManager.rollBack( null );
150             throw new IdentityStoreException( e.getMessage( ), e, MESSAGE_KEY_BATCH_ERROR_DURING_CREATION );
151         }
152     }
153 
154     public int importBatchFromIhm(final BatchDto batch, final User user, final String feedToken) throws IdentityStoreException
155     {
156         // Ensure that provided batch size does not exceed the limit defined in properties
157         validationService.validateImportBatchLimit( batch );
158 
159         TransactionManager.beginTransaction( null );
160 
161         try
162         {
163             // Validate the batch definition and compliance to client service contract
164             if ( StringUtils.isNotEmpty( feedToken ) )
165             {
166                 progressManagerService.initFeed( feedToken, batch.getIdentities( ).size( ) );
167                 progressManagerService.addReport( feedToken, "Validating batch ..." );
168             }
169             validationService.validateBatchFromIhm(batch);
170 
171             // Try to retrieve the batch by its reference, if exists ensure that both side information is consistent, if not, create it.
172             if ( StringUtils.isNotEmpty( feedToken ) )
173             {
174                 progressManagerService.addReport( feedToken, "Creating batch..." );
175             }
176 
177             if ( BatchHome.getBatch( batch.getReference( ) ) != null )
178             {
179                 throw new IdentityStoreException( "A batch already exists with this reference.", MESSAGE_KEY_BATCH_EXISTS_WITH_SAME_REFERENCE );
180             }
181             final Batch bean = this.getBean( batch );
182             bean.setCreationDate( Timestamp.from( Instant.now( ) ) );
183             BatchHome.create( bean );
184 
185             // Init workflow resource
186             final int batchId = bean.getId( );
187             final WorkflowBean<Batch> batchWorkflowBean = _wfBatchBeanService.createWorkflowBean( bean, batchId, user );
188 
189             if ( StringUtils.isNotEmpty( feedToken ) )
190             {
191                 progressManagerService.addReport( feedToken, "Batch created with id " + batchId );
192             }
193 
194             // Import identities
195             if ( StringUtils.isNotEmpty( feedToken ) )
196             {
197                 progressManagerService.addReport( feedToken, "Creating candidate identities..." );
198             }
199             for ( final IdentityDto identity : batch.getIdentities( ) )
200             {
201                 final CandidateIdentityess/CandidateIdentity.html#CandidateIdentity">CandidateIdentity candidateIdentity = new CandidateIdentity( );
202                 candidateIdentity.setIdBatch( batchId );
203                 candidateIdentity.setExternalCustomerId( identity.getExternalCustomerId( ) );
204                 candidateIdentity.setConnectionId( identity.getConnectionId( ) );
205                 candidateIdentity.setClientCode( bean.getClientCode( ) );
206                 CandidateIdentityHome.create( candidateIdentity );
207                 _wfIdentityBeanService.createWorkflowBean( candidateIdentity, candidateIdentity.getId( ), candidateIdentity.getIdBatch( ), user );
208 
209                 for ( final AttributeDto importedAttribute : identity.getAttributes( ) )
210                 {
211                     final CandidateIdentityAttributeateIdentityAttribute.html#CandidateIdentityAttribute">CandidateIdentityAttribute candidateAttribute = new CandidateIdentityAttribute( );
212                     candidateAttribute.setIdIdentity( candidateIdentity.getId( ) );
213                     candidateAttribute.setCode( importedAttribute.getKey( ) );
214                     candidateAttribute.setValue( importedAttribute.getValue( ) );
215                     candidateAttribute.setCertDate( Timestamp.from( importedAttribute.getCertificationDate( ).toInstant( ) ) );
216                     candidateAttribute.setCertProcess( importedAttribute.getCertifier( ) );
217                     CandidateIdentityAttributeHome.create( candidateAttribute );
218                 }
219                 if ( StringUtils.isNotEmpty( feedToken ) )
220                 {
221                     progressManagerService.incrementSuccess( feedToken, 1 );
222                 }
223             }
224             if ( StringUtils.isNotEmpty( feedToken ) )
225             {
226                 progressManagerService.addReport( feedToken, "Created" + batch.getIdentities( ).size( ) + " candidate identities" );
227             }
228             TransactionManager.commitTransaction( null );
229             _wfBatchBeanService.processActionNoUser( batchWorkflowBean, VALIDATE_BATCH_ACTION_ID, null, Locale.getDefault( ) );
230             return bean.getId( );
231         }
232         catch( final Exception e )
233         {
234             TransactionManager.rollBack( null );
235             if ( StringUtils.isNotEmpty( feedToken ) )
236             {
237                 progressManagerService.addReport( feedToken, "An error occurred during creation..." );
238             }
239             if ( e instanceof IdentityStoreException )
240             {
241                 throw e;
242             }
243             throw new IdentityStoreException( e.getMessage( ), e, MESSAGE_KEY_BATCH_ERROR_DURING_CREATION );
244         }
245     }
246 
247     /**
248      * Find expired batches (limited to batchLimit param) and purge them. Deletion of {@link CandidateIdentity}, {@link CandidateIdentityAttribute},
249      * {@link CandidateIdentityHistory}
250      * 
251      * @param batchLimit
252      * @return
253      */
254     public StringBuilder purgeBatches( final int batchLimit ) throws IdentityStoreException
255     {
256         final StringBuilder msg = new StringBuilder( );
257         final List<Batch> expiredBatches = BatchHome.findExpiredBatches( batchLimit );
258         msg.append( expiredBatches.size( ) ).append( " expired batches found" ).append( System.lineSeparator( ) );
259 
260         if ( ARCHIVE_ACTION_ID > 0 )
261         {
262             for ( final Batch expiredBatch : expiredBatches )
263             {
264                 msg.append( "Removing expired batch : " ).append( expiredBatch.toLog( ) ).append( System.lineSeparator( ) );
265                 final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( expiredBatch, expiredBatch.getId( ), null );
266                 _wfBatchBeanService.processAutomaticAction( workflowBean, ARCHIVE_ACTION_ID, null, Locale.getDefault( ) );
267             }
268         }
269         else
270         {
271             AppLogService.error( "Property identityimport.archive.action.id must be defined in order to perform archiving task." );
272         }
273 
274         // return message for daemons
275         return msg;
276     }
277 
278     /**
279      * Purge given batch. Deletion of {@link CandidateIdentity}, {@link CandidateIdentityAttribute}, {@link CandidateIdentityHistory}
280      *
281      * @param batchId
282      *            the id of the batch
283      * @return
284      */
285     public void purgeBatch( final int batchId ) throws IdentityStoreException
286     {
287         final Optional<Batch> expiredBatch = BatchHome.findByPrimaryKey( batchId );
288         if ( expiredBatch.isPresent( ) )
289         {
290             final Batch batch = expiredBatch.get( );
291             TransactionManager.beginTransaction( null );
292 
293             try
294             {
295                 final List<Integer> idCandidateIdentitiesList = CandidateIdentityHome.getIdCandidateIdentitiesList( batch.getId( ), null );
296                 CandidateIdentityHome.delete( idCandidateIdentitiesList );
297                 CandidateIdentityAttributeHome.delete( idCandidateIdentitiesList );
298                 CandidateIdentityHistoryHome.delete( idCandidateIdentitiesList );
299                 TransactionManager.commitTransaction( null );
300             }
301             catch( final Exception e )
302             {
303                 TransactionManager.rollBack( null );
304                 throw new IdentityStoreException( "An error occurred during batch purge.", e );
305             }
306         }
307         else
308         {
309             throw new IdentityStoreException( "Could not find batch with ID " + batchId );
310         }
311     }
312 
313     /**
314      * Find newly created batches (limited to batchLimit param) and launch them automatically.
315      *
316      * @param batchLimit
317      *            the limit
318      * @return logs
319      */
320     public StringBuilder launchBatches( final int batchLimit )
321     {
322         final StringBuilder msg = new StringBuilder( );
323         try
324         {
325             final List<Batch> initialStateBatches = BatchHome.findInitialStateBatches( batchLimit );
326             if ( !initialStateBatches.isEmpty( ) )
327             {
328                 msg.append( initialStateBatches.size( ) ).append( " initial state batches found" ).append( System.lineSeparator( ) );
329                 final int actionId = BatchHome.getBatchInitialActionId( );
330                 for ( final Batch batch : initialStateBatches )
331                 {
332                     msg.append( "Launching batch : " ).append( batch.toLog( ) ).append( System.lineSeparator( ) );
333                     final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( batch, batch.getId( ), null );
334                     _wfBatchBeanService.processAutomaticAction( workflowBean, actionId, null, Locale.getDefault( ) );
335                 }
336             }
337             else
338             {
339                 msg.append( "No initial state batches found." ).append( System.lineSeparator( ) );
340             }
341         }
342         catch( final Exception e )
343         {
344             msg.append( "Error occurred while launching batches automatically :: " ).append( System.lineSeparator( ) ).append( e.getMessage( ) )
345                     .append( System.lineSeparator( ) );
346         }
347         return msg;
348     }
349 
350     /**
351      * Find batches that can be closed, and close them
352      * 
353      * @param batchLimit
354      *            the limit
355      * @return logs
356      */
357     public StringBuilder closeBatches( int batchLimit )
358     {
359         final StringBuilder msg = new StringBuilder( );
360         try
361         {
362             final List<Batch> closableBatches = BatchHome.findClosableBatches( batchLimit );
363             if ( !closableBatches.isEmpty( ) )
364             {
365                 msg.append( closableBatches.size( ) ).append( " in treatment state batches found" ).append( System.lineSeparator( ) );
366                 final int actionId = BatchHome.getBatchInTreatmentActionId( );
367                 for ( final Batch batch : closableBatches )
368                 {
369                     msg.append( "Closing batch : " ).append( batch.toLog( ) ).append( System.lineSeparator( ) );
370                     final WorkflowBean<Batch> workflowBean = _wfBatchBeanService.createWorkflowBean( batch, batch.getId( ), null );
371                     _wfBatchBeanService.processAutomaticAction( workflowBean, actionId, null, Locale.getDefault( ) );
372                 }
373             }
374             else
375             {
376                 msg.append( "No in treatment state batches found." ).append( System.lineSeparator( ) );
377             }
378         }
379         catch( final Exception e )
380         {
381             msg.append( "Error occurred while closing batches automatically :: " ).append( System.lineSeparator( ) ).append( e.getMessage( ) )
382                     .append( System.lineSeparator( ) );
383         }
384         return msg;
385     }
386 
387     public Batch getBean( BatchDto batch )
388     {
389         final Batchgins/identityimport/business/Batch.html#Batch">Batch bean = new Batch( );
390         bean.setReference( batch.getReference( ) );
391         bean.setComment( batch.getComment( ) );
392         bean.setCreationDate( batch.getCreationDate( ) );
393         bean.setUser( batch.getUser( ) );
394         bean.setAppCode( batch.getAppCode( ) );
395         bean.setClientCode( batch.getClientCode( ) );
396         return bean;
397     }
398 
399     public BatchDto getDto( Batch batch )
400     {
401         final BatchDto dto = new BatchDto( );
402         dto.setReference( batch.getReference( ) );
403         dto.setComment( batch.getComment( ) );
404         dto.setCreationDate( batch.getCreationDate( ) );
405         dto.setUser( batch.getUser( ) );
406         dto.setAppCode( batch.getAppCode( ) );
407         dto.setClientCode( batch.getClientCode( ) );
408         return dto;
409     }
410 
411     public BatchStatusDto getBatchStatus( final String strBatchReference, final BatchStatusMode mode ) throws IdentityStoreException
412     {
413         final Batch batch = BatchHome.getBatch( strBatchReference );
414         if ( batch == null )
415         {
416             throw new ResourceNotFoundException("Batch not found", Constants.PROPERTY_REST_ERROR_BATCH_NOT_FOUND);
417         }
418         final ResourceState batchState = BatchHome.getBatchState( batch.getId( ) );
419         if ( batchState == null )
420         {
421             throw new ResourceNotFoundException("State of batch not found.", Constants.PROPERTY_REST_ERROR_BATCH_STATE_NOT_FOUND);
422         }
423 
424         final BatchStatusDto batchStatus = new BatchStatusDto( );
425 
426         batchStatus.setReference( batch.getReference( ) );
427         batchStatus.setClientCode( batch.getClientCode( ) );
428         batchStatus.setUser( batch.getUser( ) );
429         batchStatus.setComment( batch.getComment( ) );
430         batchStatus.setCreationDate( batch.getCreationDate( ) );
431         batchStatus.setStatus( batchState.getName( ) );
432         batchStatus.setStatusDescription( batchState.getDescription( ) );
433 
434         final BatchStatisticsDto statisticsDto = new BatchStatisticsDto( );
435         batchStatus.setStatistics( statisticsDto );
436         final List<ResourceState> candidateIdentityStates = CandidateIdentityHome.getCandidateIdentityStates( batch.getId( ) );
437         candidateIdentityStates.stream( ).filter( r -> r.getResourceCount( ) > 0 ).forEach( resourceState -> {
438             final BatchResourceStateDto resourceStateDto = new BatchResourceStateDto( );
439             statisticsDto.getResourceStates( ).add( resourceStateDto );
440             resourceStateDto.setName( resourceState.getName( ) );
441             resourceStateDto.setDescription( resourceState.getDescription( ) );
442             resourceStateDto.setInitialState( resourceStateDto.isInitialState( ) );
443             resourceStateDto.setResourceCount( resourceState.getResourceCount( ) );
444         } );
445 
446         statisticsDto.setTotalResourceCount( candidateIdentityStates.stream( ).mapToInt( ResourceState::getResourceCount ).sum( ) );
447 
448         if ( mode != BatchStatusMode.IDENTITIES_ONLY )
449         {
450             final List<ResourceHistory> batchHistory = BatchHome.getBatchHistory( batch.getId( ) );
451             for ( final ResourceHistory history : batchHistory )
452             {
453                 final ImportingHistoryDto historyDto = new ImportingHistoryDto( );
454                 historyDto.setActionName( history.getAction( ).getName( ) );
455                 historyDto.setActionDescription( history.getAction( ).getDescription( ) );
456                 historyDto.setDate( history.getCreationDate( ) );
457                 historyDto.setUserAccessCode( history.getUserAccessCode( ) );
458                 batchStatus.getBatchHistory( ).add( historyDto );
459             }
460         }
461         if ( mode != BatchStatusMode.BATCH_ONLY )
462         {
463             final List<CandidateIdentity> identities = CandidateIdentityHome
464                     .getCandidateIdentitiesListByIds( CandidateIdentityHome.getIdCandidateIdentitiesList( batch.getId( ), null ) );
465             identities.forEach( candidate -> {
466                 final List<CandidateIdentityHistory> identityHistoryList = CandidateIdentityHistoryHome.selectAll( candidate.getId( ) );
467                 final List<ResourceHistory> history = CandidateIdentityHome.getHistory( candidate.getId( ) );
468                 batchStatus.getIdentities( ).add( CandidateIdentityService.instance( ).getDto( candidate, identityHistoryList, history ) );
469             } );
470         }
471 
472         return batchStatus;
473     }
474 }