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.service;
35  
36  import fr.paris.lutece.plugins.grubusiness.business.demand.DemandType;
37  import fr.paris.lutece.plugins.grubusiness.business.web.rs.DemandDisplay;
38  import fr.paris.lutece.plugins.identitystore.business.identity.Identity;
39  import fr.paris.lutece.plugins.identitystore.business.identity.IdentityHome;
40  import fr.paris.lutece.plugins.identitystore.cache.DemandTypeCacheService;
41  import fr.paris.lutece.plugins.identitystore.service.contract.ServiceContractService;
42  import fr.paris.lutece.plugins.identitystore.service.identity.IdentityService;
43  import fr.paris.lutece.plugins.identitystore.service.listeners.IdentityStoreNotifyListenerService;
44  import fr.paris.lutece.plugins.identitystore.service.user.InternalUserService;
45  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.RequestAuthor;
46  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.history.IdentityChangeType;
47  import fr.paris.lutece.plugins.notificationstore.v1.web.service.NotificationStoreService;
48  import fr.paris.lutece.portal.service.security.AccessLogService;
49  import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
50  import fr.paris.lutece.portal.service.spring.SpringContextService;
51  import fr.paris.lutece.portal.service.util.AppLogService;
52  import org.apache.commons.lang3.StringUtils;
53  
54  import java.sql.Timestamp;
55  import java.time.Instant;
56  import java.time.ZoneId;
57  import java.time.ZonedDateTime;
58  import java.util.ArrayList;
59  import java.util.Collections;
60  import java.util.List;
61  
62  import static fr.paris.lutece.plugins.identitystore.service.identity.IdentityService.UPDATE_IDENTITY_EVENT_CODE;
63  
64  public final class PurgeIdentityService
65  {
66      private static PurgeIdentityService _instance;
67  
68      private final NotificationStoreService _notificationStoreService = SpringContextService.getBean( "notificationStore.notificationStoreService" );
69      private final DemandTypeCacheService _demandTypeCacheService = SpringContextService.getBean( "identitystore.demandTypeCacheService" );
70  
71      public static PurgeIdentityService getInstance( )
72      {
73          if ( _instance == null )
74          {
75              _instance = new PurgeIdentityService( );
76          }
77          return _instance;
78      }
79  
80      /**
81       * purge identities
82       *
83       * @return log {@link StringBuilder}
84       */
85      public String purge( final RequestAuthor daemonAuthor, final String daemonClientCode, final List<String> excludedAppCodes, final int batchLimit )
86      {
87          final StringBuilder msg = new StringBuilder( );
88  
89          // search identities with a passed peremption date, not merged to a primary identity, and not associated to a MonParis account
90          final List<Identity> expiredIdentities = IdentityHome.findExpiredNotMergedAndNotConnectedIdentities( batchLimit );
91          final Timestamp now = Timestamp.from( Instant.now( ) );
92  
93          msg.append( expiredIdentities.size( ) ).append( " expired identities found" ).append( "\n" );
94  
95          for ( final Identity expiredIdentity : expiredIdentities )
96          {
97              try
98              {
99                  final List<Identity> mergedIdentities = IdentityHome.findMergedIdentities( expiredIdentity.getId( ) );
100                 // - check if exists recent Demands associated to each identity or its merged ones
101                 // >> if true, calculate the new expiration date (date of demand last update + CGUs term)
102                 final List<DemandDisplay> demandDisplayList = new ArrayList<>(
103                         _notificationStoreService.getListDemand( expiredIdentity.getCustomerId( ), null, null, null, null ).getListDemandDisplay( ) );
104                 for ( final Identity mergedIdentity : mergedIdentities )
105                 {
106                     demandDisplayList
107                             .addAll( _notificationStoreService.getListDemand( mergedIdentity.getCustomerId( ), null, null, null, null ).getListDemandDisplay( ) );
108                 }
109 
110                 Timestamp demandExpirationDateMAX = expiredIdentity.getExpirationDate( );
111                 for ( final DemandDisplay demand : demandDisplayList )
112                 {
113                     final String appCode = getAppCodeFromDemandTypeId( demand.getDemand( ).getTypeId( ) );
114                     if ( !excludedAppCodes.contains( appCode ) )
115                     {
116                         final List<String> clientCodeList = ServiceContractService.instance( ).getClientCodesFromAppCode( appCode );
117 
118                         int nbMonthsCGUsMAX = 0;
119                         for ( final String clientCode : clientCodeList )
120                         {
121                             // if there is more than one client code for the app_code, keep the max value of cgus
122                             int nbMonthsCGUs = ServiceContractService.instance( ).getDataRetentionPeriodInMonths( clientCode );
123                             if ( nbMonthsCGUs > nbMonthsCGUsMAX )
124                             {
125                                 nbMonthsCGUsMAX = nbMonthsCGUs;
126                             }
127                         }
128 
129                         final ZonedDateTime demandDate = ZonedDateTime.ofInstant( Instant.ofEpochMilli( demand.getDemand( ).getModifyDate( ) ),
130                                 ZoneId.systemDefault( ) );
131                         final Timestamp expirationDateFromDemand = Timestamp.from( demandDate.plusMonths( nbMonthsCGUsMAX ).toInstant( ) );
132 
133                         // keep the max expiration date
134                         if ( demandExpirationDateMAX.before( expirationDateFromDemand ) )
135                         {
136                             demandExpirationDateMAX = expirationDateFromDemand;
137                         }
138                     }
139                 }
140 
141                 // check if expiredIdentity should be preserved or can be deleted
142                 if ( demandExpirationDateMAX.after( now ) )
143                 {
144                     // expiration date calculated from most recent demand is later than today
145                     // => update the expiration date of expiredIdentity : it will be deleted later
146                     expiredIdentity.setExpirationDate( demandExpirationDateMAX );
147                     IdentityHome.update( expiredIdentity );
148 
149                     // re-index and add history
150                     IdentityStoreNotifyListenerService.instance( ).notifyListenersIdentityChange( IdentityChangeType.UPDATE, expiredIdentity,
151                             "EXPIRATION_POSTPONED", StringUtils.EMPTY, daemonAuthor, daemonClientCode, Collections.emptyMap( ) );
152                     AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_MODIFY, UPDATE_IDENTITY_EVENT_CODE,
153                             InternalUserService.getInstance( ).getApiUser( daemonAuthor, daemonClientCode ), null, "PURGE_DAEMON" );
154 
155                     msg.append( "Identity expiration date updated : [" ).append( expiredIdentity.getCustomerId( ) ).append( "]" ).append( "\n" );
156                 }
157                 else
158                 {
159                     // if the peremption date still passed, delete the identity (and children as merged identities,
160                     // suspicious, attributes and attributes history, etc ...) EXCEPT the identity history
161                     IdentityService.instance( ).delete( expiredIdentity.getCustomerId( ) );
162                     msg.append( "Identity deleted for [" ).append( expiredIdentity.getCustomerId( ) ).append( "]" ).append( "\n" );
163 
164                     // delete notifications
165                     _notificationStoreService.deleteNotificationByCuid( expiredIdentity.getCustomerId( ) );
166                     msg.append( "Notifications deleted for main identity [" ).append( expiredIdentity.getCustomerId( ) ).append( "]" ).append( "\n" );
167                     for ( final Identity mergedIdentity : mergedIdentities )
168                     {
169                         _notificationStoreService.deleteNotificationByCuid( mergedIdentity.getCustomerId( ) );
170                         msg.append( "Notifications deleted for merged identity [" ).append( mergedIdentity.getCustomerId( ) ).append( "]" ).append( "\n" );
171                     }
172                 }
173             }
174             catch( final Exception e )
175             {
176                 msg.append( "Daemon execution error for identity : " ).append( expiredIdentity.getCustomerId( ) ).append( " :: " ).append( e.getMessage( ) )
177                         .append( "\n" );
178                 return msg.toString( );
179             }
180         }
181 
182         // return message for daemons
183         return msg.toString( );
184     }
185 
186     /**
187      * get app code
188      *
189      * @param strTypeId
190      *            the type id
191      * @return the app code
192      */
193     private String getAppCodeFromDemandTypeId( final String strTypeId )
194     {
195         DemandType demandType = _demandTypeCacheService.getResource( strTypeId );
196         if ( demandType == null )
197         {
198             // refresh cache & search again
199             demandType = reinitDemandTypeCacheAndGetDemandType( strTypeId );
200             if ( demandType == null )
201             {
202                 return null;
203             }
204         }
205         return demandType.getAppCode( );
206     }
207 
208     /**
209      * reinit Demand Type cache & search mandatory DemandType
210      *
211      * @param strDemandTypeId
212      * @return the DemandType
213      */
214     private DemandType reinitDemandTypeCacheAndGetDemandType( String strDemandTypeId )
215     {
216         DemandType foundDemandType = null;
217         try
218         {
219             for ( final DemandType demand : _notificationStoreService.getDemandTypes( ) )
220             {
221                 if ( _demandTypeCacheService.isCacheEnable( ) )
222                 {
223                     // refresh cache
224                     _demandTypeCacheService.putResourceInCache( demand );
225                 }
226                 if ( strDemandTypeId.equals( String.valueOf( demand.getIdDemandType( ) ) ) )
227                 {
228                     foundDemandType = demand;
229                     if ( !_demandTypeCacheService.isCacheEnable( ) )
230                     {
231                         return foundDemandType;
232                     }
233                 }
234             }
235         }
236         catch( final Exception e )
237         {
238             AppLogService.debug( "Notification Store Service Error", e );
239         }
240         return foundDemandType;
241     }
242 }