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.identitystore.web;
35  
36  import com.fasterxml.jackson.databind.DeserializationFeature;
37  import com.fasterxml.jackson.databind.ObjectMapper;
38  import com.fasterxml.jackson.databind.SerializationFeature;
39  import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentityHome;
40  import fr.paris.lutece.plugins.identitystore.business.identity.Identity;
41  import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttributeHome;
42  import fr.paris.lutece.plugins.identitystore.business.identity.IdentityHome;
43  import fr.paris.lutece.plugins.identitystore.service.IdentityManagementResourceIdService;
44  import fr.paris.lutece.plugins.identitystore.service.identity.IdentityService;
45  import fr.paris.lutece.plugins.identitystore.service.search.ISearchIdentityService;
46  import fr.paris.lutece.plugins.identitystore.utils.Batch;
47  import fr.paris.lutece.plugins.identitystore.v3.csv.CsvIdentityService;
48  import fr.paris.lutece.plugins.identitystore.v3.web.rs.DtoConverter;
49  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeTreatmentType;
50  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.BatchDto;
51  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
52  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.AttributeChange;
53  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.IdentityChange;
54  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.importing.BatchImportRequest;
55  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.SearchAttribute;
56  import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
57  import fr.paris.lutece.plugins.identitystore.web.exception.IdentityStoreException;
58  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
59  import fr.paris.lutece.portal.service.security.AccessLogService;
60  import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
61  import fr.paris.lutece.portal.service.spring.SpringContextService;
62  import fr.paris.lutece.portal.service.util.AppPropertiesService;
63  import fr.paris.lutece.portal.util.mvc.admin.annotations.Controller;
64  import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
65  import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
66  import fr.paris.lutece.util.http.SecurityUtil;
67  import org.apache.commons.collections.CollectionUtils;
68  import org.apache.commons.collections.MapUtils;
69  import org.apache.commons.lang3.StringUtils;
70  
71  import javax.servlet.http.HttpServletRequest;
72  import java.io.ByteArrayOutputStream;
73  import java.sql.Date;
74  import java.time.LocalDate;
75  import java.util.ArrayList;
76  import java.util.Collections;
77  import java.util.List;
78  import java.util.Map;
79  import java.util.Objects;
80  import java.util.Optional;
81  import java.util.UUID;
82  import java.util.stream.Collectors;
83  import java.util.zip.ZipEntry;
84  import java.util.zip.ZipOutputStream;
85  
86  /**
87   * This class provides the user interface to manage Identity features ( manage, create, modify, remove )
88   */
89  @Controller( controllerJsp = "ManageIdentities.jsp", controllerPath = "jsp/admin/plugins/identitystore/", right = "IDENTITYSTORE_MANAGEMENT" )
90  public class IdentityJspBean extends ManageIdentitiesJspBean
91  {
92      /**
93       * 
94       */
95      private static final long serialVersionUID = 6053504380426222888L;
96      // Templates
97      private static final String TEMPLATE_SEARCH_IDENTITIES = "/admin/plugins/identitystore/search_identities.html";
98      private static final String TEMPLATE_VIEW_IDENTITY = "/admin/plugins/identitystore/view_identity.html";
99      private static final String TEMPLATE_VIEW_IDENTITY_HISTORY = "/admin/plugins/identitystore/view_identity_change_history.html";
100 
101     // Parameters
102     private static final String PARAMETER_ID_IDENTITY = "id";
103 
104     // Properties for page titles
105     private static final String PROPERTY_PAGE_TITLE_MANAGE_IDENTITIES = "identitystore.manage_identities.pageTitle";
106     private static final String PROPERTY_PAGE_TITLE_VIEW_IDENTITY = "identitystore.create_identity.pageTitle";
107     private static final String PROPERTY_PAGE_TITLE_VIEW_CHANGE_HISTORY = "identitystore.view_change_history.pageTitle";
108 
109     // Markers
110     private static final String MARK_IDENTITY_LIST = "identity_list";
111     private static final String MARK_IDENTITY = "identity";
112     private static final String MARK_IDENTITY_IS_SUSPICIOUS = "identity_is_suspicious";
113     private static final String MARK_IDENTITY_CHANGE_LIST = "identity_change_list";
114     private static final String MARK_ATTRIBUTES_CHANGE_LIST = "attributes_change_list";
115     private static final String MARK_HAS_CREATE_ROLE = "createIdentityRole";
116     private static final String MARK_HAS_MODIFY_ROLE = "modifyIdentityRole";
117     private static final String MARK_HAS_DELETE_ROLE = "deleteIdentityRole";
118     private static final String MARK_HAS_VIEW_ROLE = "viewIdentityRole";
119     private static final String MARK_HAS_ATTRIBUTS_HISTO_ROLE = "histoAttributsRole";
120     private static final String JSP_MANAGE_IDENTITIES = "jsp/admin/plugins/identitystore/ManageIdentities.jsp";
121 
122     // Views
123     private static final String VIEW_MANAGE_IDENTITIES = "manageIdentitys";
124     private static final String VIEW_IDENTITY = "viewIdentity";
125     private static final String VIEW_IDENTITY_HISTORY = "viewIdentityHistory";
126 
127     // Actions
128     private static final String ACTION_EXPORT_IDENTITIES = "exportIdentities";
129     private static final String ACTION_BATCH_GENERATE_REQUESTS = "exportRequestIdentities";
130 
131     // Events
132     private static final String DISPLAY_IDENTITY_EVENT_CODE = "DISPLAY_IDENTITY";
133     private static final String DISPLAY_IDENTITY_HISTORY_EVENT_CODE = "DISPLAY_HISTORY_IDENTITY";
134 
135     // Datasource
136     private static final String DATASOURCE_DB = "db";
137     private static final String DATASOURCE_ES = "es";
138     private static final int BATCH_PARTITION_SIZE = AppPropertiesService.getPropertyInt( "identitystore.export.batch.size", 100 );
139 
140     // Session variable to store working values
141     private Identity _identity;
142 
143     private final List<IdentityDto> _identities = new ArrayList<>( );
144 
145     private final ISearchIdentityService _searchIdentityServiceDB = SpringContextService.getBean( "identitystore.searchIdentityService.database" );
146     private final ISearchIdentityService _searchIdentityServiceES = SpringContextService.getBean( "identitystore.searchIdentityService.elasticsearch" );
147 
148     @View( value = VIEW_MANAGE_IDENTITIES, defaultView = true )
149     public String getManageIdentitys( HttpServletRequest request )
150     {
151         _identity = null;
152         _identities.clear( );
153         final Map<String, String> queryParameters = this.getQueryParameters( request );
154 
155         final List<SearchAttribute> atttributes = new ArrayList<>( );
156         final String cuid = queryParameters.get( QUERY_PARAM_CUID );
157         final String guid = queryParameters.get( QUERY_PARAM_GUID );
158         final String email = queryParameters.get( QUERY_PARAM_EMAIL );
159         final String gender = queryParameters.get( QUERY_PARAM_GENDER );
160         final String family_name = queryParameters.get( QUERY_PARAM_FAMILY_NAME );
161         final String preferred_username = queryParameters.get( QUERY_PARAM_PREFERRED_USERNAME );
162         final String first_name = queryParameters.get( QUERY_PARAM_FIRST_NAME );
163         final String birthdate = queryParameters.get( QUERY_PARAM_BIRTHDATE );
164         final String birthplace = queryParameters.get( QUERY_PARAM_INSEE_BIRTHPLACE_LABEL );
165         final String birthcountry = queryParameters.get( QUERY_PARAM_INSEE_BIRTHCOUNTRY_LABEL );
166         final String phone = queryParameters.get( QUERY_PARAM_PHONE );
167         final String datasource = Optional.ofNullable( queryParameters.get( QUERY_PARAM_DATASOURCE ) ).orElse( DATASOURCE_DB );
168 
169         try
170         {
171             if ( StringUtils.isNotEmpty( cuid ) )
172             {
173                 final Identity identity = IdentityHome.findMasterIdentityByCustomerId( cuid );
174                 if ( identity != null )
175                 {
176                     final IdentityDto qualifiedIdentity = DtoConverter.convertIdentityToDto( identity );
177                     _identities.add( qualifiedIdentity );
178                 }
179             }
180             else
181             {
182                 if ( StringUtils.isNotEmpty( guid ) )
183                 {
184                     final Identity identity = IdentityHome.findMasterIdentityByConnectionId( guid );
185                     if ( identity != null )
186                     {
187                         final IdentityDto qualifiedIdentity = DtoConverter.convertIdentityToDto( identity );
188                         _identities.add( qualifiedIdentity );
189                     }
190                 }
191                 else
192                 {
193                     if ( StringUtils.isNotEmpty( email ) )
194                     {
195                         atttributes.add( new SearchAttribute( Constants.PARAM_LOGIN, email, AttributeTreatmentType.STRICT ) );
196                     }
197                     if ( StringUtils.isNotEmpty( gender ) )
198                     {
199                         atttributes.add( new SearchAttribute( Constants.PARAM_GENDER, gender, AttributeTreatmentType.STRICT ) );
200                     }
201                     if ( StringUtils.isNotEmpty( family_name ) )
202                     {
203                         atttributes.add( new SearchAttribute( Constants.PARAM_FAMILY_NAME, family_name, AttributeTreatmentType.APPROXIMATED ) );
204                     }
205                     if ( StringUtils.isNotEmpty( preferred_username ) )
206                     {
207                         atttributes.add( new SearchAttribute( Constants.PARAM_PREFERRED_USERNAME, preferred_username, AttributeTreatmentType.APPROXIMATED ) );
208                     }
209                     if ( StringUtils.isNotEmpty( first_name ) )
210                     {
211                         atttributes.add( new SearchAttribute( Constants.PARAM_FIRST_NAME, first_name, AttributeTreatmentType.APPROXIMATED ) );
212                     }
213                     if ( StringUtils.isNotEmpty( birthdate ) )
214                     {
215                         atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_DATE, birthdate, AttributeTreatmentType.STRICT ) );
216                     }
217                     if ( StringUtils.isNotEmpty( birthplace ) )
218                     {
219                         atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_PLACE, birthplace, AttributeTreatmentType.STRICT ) );
220                     }
221                     if ( StringUtils.isNotEmpty( birthcountry ) )
222                     {
223                         atttributes.add( new SearchAttribute( Constants.PARAM_BIRTH_COUNTRY, birthcountry, AttributeTreatmentType.STRICT ) );
224                     }
225                     if ( StringUtils.isNotEmpty( phone ) )
226                     {
227                         atttributes.add( new SearchAttribute( Constants.PARAM_MOBILE_PHONE, phone, AttributeTreatmentType.STRICT ) );
228                     }
229                     if ( CollectionUtils.isNotEmpty( atttributes ) )
230                     {
231                         if ( datasource.equals( DATASOURCE_DB ) )
232                         {
233                             _identities.addAll( _searchIdentityServiceDB.getQualifiedIdentities( atttributes, 0, false, Collections.emptyList( ) )
234                                     .getQualifiedIdentities( ) );
235                         }
236                         else
237                             if ( datasource.equals( DATASOURCE_ES ) )
238                             {
239                                 _identities.addAll( _searchIdentityServiceES.getQualifiedIdentities( atttributes, 0, false, Collections.emptyList( ) )
240                                         .getQualifiedIdentities( ) );
241                             }
242                     }
243                 }
244             }
245         }
246         catch( Exception e )
247         {
248             addError( e.getMessage( ) );
249             this.clearParameters( request );
250             return redirectView( request, VIEW_MANAGE_IDENTITIES );
251         }
252 
253         _identities.forEach(
254                 qualifiedIdentity -> AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, IdentityService.SEARCH_IDENTITY_EVENT_CODE,
255                         getUser( ), SecurityUtil.logForgingProtect( qualifiedIdentity.getCustomerId( ) ), IdentityService.SPECIFIC_ORIGIN ) );
256 
257         final Map<String, Object> model = getPaginatedListModel( request, MARK_IDENTITY_LIST, _identities, JSP_MANAGE_IDENTITIES );
258         model.put( MARK_HAS_CREATE_ROLE,
259                 IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_CREATE_IDENTITY, getUser( ) ) );
260         model.put( MARK_HAS_MODIFY_ROLE,
261                 IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_MODIFY_IDENTITY, getUser( ) ) );
262         model.put( MARK_HAS_DELETE_ROLE,
263                 IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_DELETE_IDENTITY, getUser( ) ) );
264         model.put( MARK_HAS_VIEW_ROLE,
265                 IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_VIEW_IDENTITY, getUser( ) ) );
266 
267         model.put( QUERY_PARAM_CUID, cuid );
268         model.put( QUERY_PARAM_GUID, guid );
269         model.put( QUERY_PARAM_FAMILY_NAME, family_name );
270         model.put( QUERY_PARAM_PREFERRED_USERNAME, preferred_username );
271         model.put( QUERY_PARAM_FIRST_NAME, first_name );
272         model.put( QUERY_PARAM_EMAIL, email );
273         model.put( QUERY_PARAM_INSEE_BIRTHPLACE_LABEL, birthplace );
274         model.put( QUERY_PARAM_INSEE_BIRTHCOUNTRY_LABEL, birthcountry );
275         model.put( QUERY_PARAM_PHONE, phone );
276         model.put( QUERY_PARAM_BIRTHDATE, birthdate );
277         model.put( QUERY_PARAM_GENDER, gender );
278         model.put( QUERY_PARAM_DATASOURCE, datasource );
279 
280         return getPage( PROPERTY_PAGE_TITLE_MANAGE_IDENTITIES, TEMPLATE_SEARCH_IDENTITIES, model );
281     }
282 
283     /**
284      * view identity
285      *
286      * @param request
287      *            http request
288      * @return The HTML form to view info
289      */
290     @View( VIEW_IDENTITY )
291     public String getViewIdentity( HttpServletRequest request )
292     {
293         final String nId = request.getParameter( PARAMETER_ID_IDENTITY );
294 
295         _identity = IdentityHome.findByCustomerId( nId );
296         final String filteredCustomerId = SecurityUtil.logForgingProtect( _identity.getCustomerId( ) );
297         AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, DISPLAY_IDENTITY_EVENT_CODE, getUser( ), filteredCustomerId,
298                 IdentityService.SPECIFIC_ORIGIN );
299 
300         final Map<String, Object> model = getModel( );
301         model.put( MARK_IDENTITY, _identity );
302         model.put( MARK_IDENTITY_IS_SUSPICIOUS, SuspiciousIdentityHome.hasSuspicious( Collections.singletonList( _identity.getCustomerId( ) ) ) );
303         model.put( MARK_HAS_ATTRIBUTS_HISTO_ROLE,
304                 IdentityManagementResourceIdService.isAuthorized( IdentityManagementResourceIdService.PERMISSION_ATTRIBUTS_HISTO, getUser( ) ) );
305 
306         return getPage( PROPERTY_PAGE_TITLE_VIEW_IDENTITY, TEMPLATE_VIEW_IDENTITY, model );
307     }
308 
309     /**
310      * Build the attribute history View
311      *
312      * @param request
313      *            The HTTP request
314      * @return The page
315      */
316     @View( value = VIEW_IDENTITY_HISTORY )
317     public String getIdentityHistoryView( HttpServletRequest request )
318     {
319         // here we use a LinkedHashMap to have same attributs order as in viewIdentity
320         final List<AttributeChange> attributeChangeList = new ArrayList<>( );
321         final List<IdentityChange> identityChangeList = new ArrayList<>( );
322 
323         if ( _identity != null && MapUtils.isNotEmpty( _identity.getAttributes( ) ) )
324         {
325             try
326             {
327                 attributeChangeList.addAll( IdentityAttributeHome.getAttributeChangeHistory( _identity.getId( ) ) );
328                 identityChangeList.addAll( IdentityHome.findHistoryByCustomerId( _identity.getCustomerId( ) ) );
329             }
330             catch( IdentityStoreException e )
331             {
332                 addError( e.getMessage( ) );
333                 return getViewIdentity( request );
334             }
335         }
336         if ( _identity != null )
337         {
338             final String filteredCustomerId = SecurityUtil.logForgingProtect( _identity.getCustomerId( ) );
339             AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_READ, DISPLAY_IDENTITY_HISTORY_EVENT_CODE, getUser( ), filteredCustomerId,
340                     IdentityService.SPECIFIC_ORIGIN );
341         }
342 
343         final Map<String, Object> model = getModel( );
344         model.put( MARK_IDENTITY_CHANGE_LIST, identityChangeList );
345         model.put( MARK_ATTRIBUTES_CHANGE_LIST, attributeChangeList );
346 
347         return getPage( PROPERTY_PAGE_TITLE_VIEW_CHANGE_HISTORY, TEMPLATE_VIEW_IDENTITY_HISTORY, model );
348     }
349 
350     /**
351      * Process the data capture form of a new suspiciousidentity
352      *
353      * @param request
354      *            The Http Request
355      * @return The Jsp URL of the process result
356      * @throws AccessDeniedException
357      */
358     @Action( ACTION_EXPORT_IDENTITIES )
359     public void doExportIdentities( HttpServletRequest request )
360     {
361         try
362         {
363             final List<IdentityDto> identitiesToProcess = _identities.stream( ).filter( this::validateMinimumAttributes )
364                     .peek( identityDto -> identityDto.setExternalCustomerId( UUID.randomUUID( ).toString( ) ) ).collect( Collectors.toList( ) );
365             final Batch<IdentityDto> batches = Batch.ofSize( identitiesToProcess, BATCH_PARTITION_SIZE );
366 
367             final ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
368             final ZipOutputStream zipOut = new ZipOutputStream( outputStream );
369 
370             int i = 0;
371             for ( final List<IdentityDto> batch : batches )
372             {
373                 final byte [ ] bytes = CsvIdentityService.instance( ).write( batch );
374                 final ZipEntry zipEntry = new ZipEntry( "identities-" + ++i + ".csv" );
375                 zipEntry.setSize( bytes.length );
376                 zipOut.putNextEntry( zipEntry );
377                 zipOut.write( bytes );
378             }
379             zipOut.closeEntry( );
380             zipOut.close( );
381             this.download( outputStream.toByteArray( ), "identities.zip", "application/zip" );
382         }
383         catch( Exception e )
384         {
385             addError( e.getMessage( ) );
386             redirectView( request, VIEW_MANAGE_IDENTITIES );
387         }
388     }
389 
390     private boolean validateMinimumAttributes( final IdentityDto identity )
391     {
392         return this.checkAttributeExists( identity, Constants.PARAM_FAMILY_NAME ) && this.checkAttributeExists( identity, Constants.PARAM_FIRST_NAME )
393                 && this.checkAttributeExists( identity, Constants.PARAM_BIRTH_DATE );
394     }
395 
396     private boolean checkAttributeExists( final IdentityDto identity, final String attributeKey )
397     {
398         return identity.getAttributes( ).stream( )
399                 .anyMatch( attributeDto -> Objects.equals( attributeDto.getKey( ), attributeKey ) && StringUtils.isNotBlank( attributeDto.getValue( ) ) );
400     }
401 
402     /**
403      * Process the data capture form of a new suspiciousidentity
404      *
405      * @param request
406      *            The Http Request
407      * @return The Jsp URL of the process result
408      * @throws AccessDeniedException
409      */
410     @Action( ACTION_BATCH_GENERATE_REQUESTS )
411     public void doGenerateBatchIdentities( HttpServletRequest request )
412     {
413         try
414         {
415             // Prepare identities for import (clear not used params)
416             _identities.forEach( identityDto -> {
417                 identityDto.setExternalCustomerId( UUID.randomUUID( ).toString( ) );
418                 identityDto.setCustomerId( null );
419                 identityDto.setQuality( null );
420                 identityDto.setExpiration( null );
421                 identityDto.setMerge( null );
422                 identityDto.setLastUpdateDate( null );
423                 identityDto.setConnectionId( null );
424                 identityDto.setMonParisActive( null );
425                 identityDto.setCreationDate( null );
426                 identityDto.setDuplicateDefinition( null );
427                 identityDto.setSuspicious( null );
428             } );
429             final Batch<IdentityDto> batches = Batch.ofSize( _identities, BATCH_PARTITION_SIZE );
430 
431             final ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
432             final ZipOutputStream zipOut = new ZipOutputStream( outputStream );
433 
434             int i = 0;
435             final String reference = UUID.randomUUID( ).toString( );
436             final Date today = new Date( LocalDate.now( ).toEpochDay( ) );
437 
438             final ObjectMapper mapper = new ObjectMapper( );
439             mapper.enable( SerializationFeature.INDENT_OUTPUT );
440             mapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
441 
442             for ( final List<IdentityDto> batch : batches )
443             {
444                 final BatchImportRequest batchImportRequest = new BatchImportRequest( );
445                 batchImportRequest.setBatch( new BatchDto( ) );
446                 batchImportRequest.getBatch( ).setReference( reference );
447                 batchImportRequest.getBatch( ).setComment( "Batch exporté depuis identity store" );
448                 batchImportRequest.getBatch( ).setDate( today );
449                 batchImportRequest.getBatch( ).setUser( getUser( ).getEmail( ) );
450                 batchImportRequest.getBatch( ).setAppCode( "TEST" );
451                 batchImportRequest.getBatch( ).setIdentities( batch );
452                 final ZipEntry zipEntry = new ZipEntry( "identities-" + ++i + ".json" );
453                 final byte [ ] bytes = mapper.writeValueAsBytes( batchImportRequest );
454                 zipEntry.setSize( bytes.length );
455                 zipOut.putNextEntry( zipEntry );
456                 zipOut.write( bytes );
457             }
458             zipOut.closeEntry( );
459             zipOut.close( );
460             this.download( outputStream.toByteArray( ), "identity_requests.zip", "application/zip" );
461         }
462         catch( Exception e )
463         {
464             addError( e.getMessage( ) );
465             redirectView( request, VIEW_MANAGE_IDENTITIES );
466         }
467     }
468 }