1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package fr.paris.lutece.plugins.identitystore.service.identity;
35
36 import com.google.common.util.concurrent.AtomicDouble;
37 import fr.paris.lutece.plugins.identitystore.business.attribute.AttributeKey;
38 import fr.paris.lutece.plugins.identitystore.business.contract.AttributeRequirement;
39 import fr.paris.lutece.plugins.identitystore.business.contract.AttributeRight;
40 import fr.paris.lutece.plugins.identitystore.business.contract.ServiceContract;
41 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.ExcludedIdentities;
42 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentity;
43 import fr.paris.lutece.plugins.identitystore.business.duplicates.suspicions.SuspiciousIdentityHome;
44 import fr.paris.lutece.plugins.identitystore.business.identity.Identity;
45 import fr.paris.lutece.plugins.identitystore.business.identity.IdentityHome;
46 import fr.paris.lutece.plugins.identitystore.cache.QualityBaseCache;
47 import fr.paris.lutece.plugins.identitystore.service.attribute.IdentityAttributeService;
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.ConsolidateDefinition;
50 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
51 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.QualityDefinition;
52 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.duplicate.IdentityDuplicateDefinition;
53 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.duplicate.IdentityDuplicateExclusion;
54 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.duplicate.IdentityDuplicateSuspicion;
55 import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.search.SearchAttribute;
56 import fr.paris.lutece.portal.service.spring.SpringContextService;
57 import fr.paris.lutece.portal.service.util.AppPropertiesService;
58 import org.apache.commons.collections4.CollectionUtils;
59 import org.apache.commons.lang3.StringUtils;
60
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Objects;
66 import java.util.Set;
67 import java.util.concurrent.atomic.AtomicInteger;
68 import java.util.stream.Collectors;
69
70 public class IdentityQualityService
71 {
72 private static final QualityBaseCache _qualityBaseCache = SpringContextService.getBean( "identitystore.qualityBaseCache" );
73
74 private static IdentityQualityService _instance;
75
76 public static IdentityQualityService instance( )
77 {
78 if ( _instance == null )
79 {
80 _instance = new IdentityQualityService( );
81 _qualityBaseCache.refresh( );
82 }
83 return _instance;
84 }
85
86 private IdentityQualityService( )
87 {
88 }
89
90 public void enrich( final List<SearchAttribute> searchAttributes, final IdentityDto identity, final ServiceContract serviceContract, final Identity bean )
91 {
92 this.enrich( searchAttributes, identity, serviceContract, bean, true );
93 }
94
95 public void enrich( final List<SearchAttribute> searchAttributes, final IdentityDto identity, final ServiceContract serviceContract, final Identity bean,
96 final boolean computeDuplicateDefinition )
97 {
98
99 IdentityQualityService.instance( ).computeCoverage( identity, serviceContract );
100 IdentityQualityService.instance( ).computeQuality( identity );
101 IdentityQualityService.instance( ).computeMatchScore( identity, searchAttributes );
102
103
104 final List<AttributeDto> filteredAttributeValues = identity.getAttributes( ).stream( )
105 .filter( certifiedAttribute -> serviceContract.getAttributeRights( ).stream( )
106 .anyMatch( attributeRight -> StringUtils.equals( attributeRight.getAttributeKey( ).getKeyName( ), certifiedAttribute.getKey( ) )
107 && attributeRight.isReadable( ) ) )
108 .collect( Collectors.toList( ) );
109 identity.getAttributes( ).clear( );
110 identity.getAttributes( ).addAll( filteredAttributeValues );
111
112 if ( computeDuplicateDefinition )
113 {
114
115 final SuspiciousIdentity suspiciousIdentity = SuspiciousIdentityHome.selectByCustomerID( identity.getCustomerId( ) );
116 if ( suspiciousIdentity != null )
117 {
118 identity.setDuplicateDefinition( new IdentityDuplicateDefinition( ) );
119 final IdentityDuplicateSuspicion duplicateSuspicion = new IdentityDuplicateSuspicion( );
120 identity.getDuplicateDefinition( ).setDuplicateSuspicion( duplicateSuspicion );
121 duplicateSuspicion.setDuplicateRuleCode( suspiciousIdentity.getDuplicateRuleCode( ) );
122 duplicateSuspicion.setCreationDate( suspiciousIdentity.getCreationDate( ) );
123 }
124
125 final List<ExcludedIdentities> excludedIdentitiesList = SuspiciousIdentityHome.getExcludedIdentitiesList( identity.getCustomerId( ) );
126 if ( CollectionUtils.isNotEmpty( excludedIdentitiesList ) )
127 {
128 if ( identity.getDuplicateDefinition( ) == null )
129 {
130 identity.setDuplicateDefinition( new IdentityDuplicateDefinition( ) );
131 }
132 identity.getDuplicateDefinition( ).getDuplicateExclusions( ).addAll( excludedIdentitiesList.stream( ).map( excludedIdentities -> {
133 final IdentityDuplicateExclusion exclusion = new IdentityDuplicateExclusion( );
134 exclusion.setExclusionDate( excludedIdentities.getExclusionDate( ) );
135 exclusion.setAuthorName( excludedIdentities.getAuthorName( ) );
136 exclusion.setAuthorType( excludedIdentities.getAuthorType( ) );
137 final String excludedCustomerId = Objects.equals( excludedIdentities.getFirstCustomerId( ), identity.getCustomerId( ) )
138 ? excludedIdentities.getSecondCustomerId( )
139 : excludedIdentities.getFirstCustomerId( );
140 exclusion.setExcludedCustomerId( excludedCustomerId );
141 return exclusion;
142 } ).collect( Collectors.toList( ) ) );
143 }
144 }
145
146 if ( bean != null )
147 {
148 final List<Identity> mergedIdentities = IdentityHome.findMergedIdentities( bean.getId( ) );
149 if ( !mergedIdentities.isEmpty( ) )
150 {
151 final ConsolidateDefinition consolidateDefinition = new ConsolidateDefinition( );
152 for ( final Identity mergedIdentity : mergedIdentities )
153 {
154 final IdentityDto mergedDto = new IdentityDto( );
155 mergedDto.setCustomerId( mergedIdentity.getCustomerId( ) );
156 mergedDto.setConnectionId( mergedIdentity.getConnectionId( ) );
157 consolidateDefinition.getMergedIdentities( ).add( mergedDto );
158 }
159 identity.setConsolidate( consolidateDefinition );
160 }
161 }
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180 private void computeCoverage( final IdentityDto identity, final ServiceContract serviceContract )
181 {
182 final Set<String> mandatoryKeys = serviceContract.getAttributeRights( ).stream( ).filter( AttributeRight::isMandatory )
183 .map( AttributeRight::getAttributeKey ).map( AttributeKey::getKeyName ).collect( Collectors.toSet( ) );
184 final Set<String> identityKeys = identity.getAttributes( ).stream( ).map( AttributeDto::getKey ).collect( Collectors.toSet( ) );
185
186 if ( identity.getQuality( ) == null )
187 {
188 identity.setQuality( new QualityDefinition( ) );
189 }
190
191 if ( !identityKeys.containsAll( mandatoryKeys ) )
192 {
193
194 identity.getQuality( ).setCoverage( 0 );
195 }
196 else
197 {
198
199 boolean coverageMatches = identity.getAttributes( ).stream( ).noneMatch( certifiedAttribute -> {
200 final AttributeRequirement requirement = serviceContract.getAttributeRequirements( ).stream( )
201 .filter( req -> Objects.equals( req.getAttributeKey( ).getKeyName( ), certifiedAttribute.getKey( ) ) ).findFirst( ).orElse( null );
202 final int attributeLevel = certifiedAttribute.getCertificationLevel( ) != null ? certifiedAttribute.getCertificationLevel( ) : 0;
203 final int minLevel = ( requirement != null && requirement.getRefCertificationLevel( ) != null
204 && requirement.getRefCertificationLevel( ).getLevel( ) != null )
205 ? Integer.parseInt( requirement.getRefCertificationLevel( ).getLevel( ) )
206 : 0;
207 return minLevel > attributeLevel;
208 } );
209 identity.getQuality( ).setCoverage( coverageMatches ? 1 : 0 );
210 }
211 }
212
213 public void computeQuality( final IdentityDto identity )
214 {
215 if ( identity.getQuality( ) == null )
216 {
217 identity.setQuality( new QualityDefinition( ) );
218 }
219 final AtomicInteger levels = new AtomicInteger( );
220 for ( final AttributeDto attribute : identity.getAttributes( ) )
221 {
222 if ( attribute.getCertificationLevel( ) == null || attribute.getCertificationLevel( ) == 0 || StringUtils.isBlank( attribute.getValue( ) ) )
223 {
224 continue;
225 }
226 final AttributeKey attributeKey = IdentityAttributeService.instance( ).getAttributeKeySafe( attribute.getKey( ) );
227 if ( attributeKey != null && attributeKey.getKeyWeight( ) > 0 )
228 {
229 levels.addAndGet( attributeKey.getKeyWeight( ) * attribute.getCertificationLevel( ) );
230 }
231 }
232 identity.getQuality( ).setQuality( levels.doubleValue( ) / _qualityBaseCache.get( ) );
233 }
234
235 private void computeMatchScore( final IdentityDto identity, final List<SearchAttribute> searchAttributes )
236 {
237 if ( identity.getQuality( ) == null )
238 {
239 identity.setQuality( new QualityDefinition( ) );
240 }
241
242 if ( CollectionUtils.isEmpty( searchAttributes ) )
243 {
244 identity.getQuality( ).setScoring( 1.0 );
245 }
246 else
247 {
248 final AtomicDouble levels = new AtomicDouble( );
249 final AtomicDouble base = new AtomicDouble( );
250 final Map<SearchAttribute, List<AttributeKey>> attributesToProcess = new HashMap<>( );
251 for ( final SearchAttribute searchAttribute : searchAttributes )
252 {
253 AttributeKey refKey = null;
254 try
255 {
256 refKey = IdentityAttributeService.instance( ).getAttributeKey( searchAttribute.getKey( ) );
257 }
258 catch( IdentityAttributeNotFoundException e )
259 {
260
261 }
262 if ( refKey != null )
263 {
264 attributesToProcess.put( searchAttribute, Collections.singletonList( refKey ) );
265 }
266 else
267 {
268
269 final List<AttributeKey> commonAttributes = IdentityAttributeService.instance( ).getCommonAttributeKeys( searchAttribute.getKey( ) );
270 attributesToProcess.put( searchAttribute, commonAttributes );
271 }
272 }
273
274 for ( final Map.Entry<SearchAttribute, List<AttributeKey>> entry : attributesToProcess.entrySet( ) )
275 {
276 for ( final AttributeKey attributeKey : entry.getValue( ) )
277 {
278 final AttributeDto attributeDto = identity.getAttributes( ).stream( )
279 .filter( attribute -> Objects.equals( attribute.getKey( ), attributeKey.getKeyName( ) ) ).findFirst( ).orElse( null );
280 base.addAndGet( attributeKey.getKeyWeight( ) );
281 if ( attributeDto != null && attributeDto.getValue( ).equalsIgnoreCase( entry.getKey( ).getValue( ) ) )
282 {
283 levels.addAndGet( attributeKey.getKeyWeight( ) );
284 }
285 else
286 {
287 final double penalty = Double.parseDouble( AppPropertiesService.getProperty( "identitystore.identity.scoring.penalty", "0.3" ) );
288 levels.addAndGet( attributeKey.getKeyWeight( ) - ( attributeKey.getKeyWeight( ) * penalty ) );
289 }
290 }
291 }
292
293 identity.getQuality( ).setScoring( levels.doubleValue( ) / base.doubleValue( ) );
294 }
295
296 }
297 }