IdentityAttributeGeocodesAdjustmentService.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.identitystore.service.attribute;
import fr.paris.lutece.plugins.geocodes.business.City;
import fr.paris.lutece.plugins.geocodes.business.Country;
import fr.paris.lutece.plugins.geocodes.service.GeoCodesService;
import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttribute;
import fr.paris.lutece.plugins.identitystore.business.identity.IdentityAttributeHome;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatus;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeStatus;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeRequest;
import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public class IdentityAttributeGeocodesAdjustmentService
{
private static final String FRANCE_COUNTRY_CODE = "99100";
private static IdentityAttributeGeocodesAdjustmentService instance;
public static IdentityAttributeGeocodesAdjustmentService instance( )
{
if ( instance == null )
{
instance = new IdentityAttributeGeocodesAdjustmentService( );
}
return instance;
}
private IdentityAttributeGeocodesAdjustmentService( )
{
}
public List<AttributeStatus> adjustGeocodesAttributes( final IdentityChangeRequest request )
{
return adjustGeocodesAttributes( request, null );
}
public List<AttributeStatus> adjustGeocodesAttributes( final IdentityChangeRequest request, final IdentityDto existingIdentityToUpdate )
{
final List<AttributeStatus> statuses = this.adjustCountry( request );
final AttributeDto requestCountryCode = request.getIdentity( ).getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_COUNTRY_CODE ) ).findFirst( ).orElse( null );
final AttributeDto existingCountryCode = existingIdentityToUpdate == null ? null
: existingIdentityToUpdate.getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_COUNTRY_CODE ) ).findFirst( )
.orElse( null );
final String countryCode = requestCountryCode != null ? requestCountryCode.getValue( )
: ( existingCountryCode != null ? existingCountryCode.getValue( ) : null );
// If no birthcountry, assume that the city is french
if ( StringUtils.isBlank( countryCode ) || FRANCE_COUNTRY_CODE.equalsIgnoreCase( countryCode ) )
{
statuses.addAll( adjustFrenchCity( request ) );
}
else
{
statuses.addAll( adjustForeignCity( request, existingIdentityToUpdate ) );
}
if ( existingIdentityToUpdate != null )
{
// Check if there is a country change
if ( existingCountryCode != null && requestCountryCode != null
&& !Objects.equals( existingCountryCode.getValue( ), requestCountryCode.getValue( ) ) )
{
// Country change
final AttributeDto existingBirthplace = existingIdentityToUpdate.getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE ) ).findFirst( ).orElse( null );
final AttributeDto requestBirthplace = request.getIdentity( ).getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE ) ).findFirst( ).orElse( null );
if ( existingBirthplace != null && requestBirthplace == null )
{
// If there is an existing birthplace and none in the request, we add a blank birthplace to the request for it to be deleted
existingBirthplace.setValue( "" );
request.getIdentity( ).getAttributes( ).add( existingBirthplace );
}
if ( existingCountryCode.getValue( ).equals( "99110" ) )
{
// Change from France to a foreign country
final AttributeDto existingBirthplaceCode = existingIdentityToUpdate.getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE_CODE ) ).findFirst( ).orElse( null );
final AttributeDto requestBirthplaceCode = request.getIdentity( ).getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE_CODE ) ).findFirst( ).orElse( null );
if ( existingBirthplaceCode != null )
{
// If there is an existing birthplace code, we tweak the request for it to be deleted
if ( requestBirthplaceCode != null )
{
request.getIdentity( ).getAttributes( ).remove( requestBirthplaceCode );
}
existingBirthplaceCode.setValue( "" );
request.getIdentity( ).getAttributes( ).add( existingBirthplaceCode );
}
}
}
}
return statuses;
}
private List<AttributeStatus> adjustCountry( final IdentityChangeRequest request )
{
final List<AttributeStatus> attrStatusList = new ArrayList<>( );
AttributeDto sentCountryCode = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_COUNTRY_CODE ) )
.findFirst( ).orElse( null );
AttributeDto sentCountryLabel = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_COUNTRY ) )
.findFirst( ).orElse( null );
// Country code was sent
if ( sentCountryCode != null && StringUtils.isNotBlank( sentCountryCode.getValue() ) )
{
final Country country = GeoCodesService.getInstance( ).getCountryByCode( sentCountryCode.getValue( ) ).orElse( null );
if ( country == null )
{
// Country doesn't exist in Geocodes for provided code : discard attribute and notify with an AttributeStatus
request.getIdentity( ).getAttributes( ).remove( sentCountryCode );
final AttributeStatus attributeStatus = new AttributeStatus( );
attributeStatus.setKey( sentCountryCode.getKey( ) );
attributeStatus.setStatus( AttributeChangeStatus.UNKNOWN_GEOCODES_CODE );
attributeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_CODE );
attrStatusList.add( attributeStatus );
}
else
{
// Country exists in Geocodes for provided code - Adjust country label attribute if needed
final String countryGeocodesLabel = country.getValue( );
// If sent label is different than the Geocodes label, modify the request to override with the Geocodes value
if ( sentCountryLabel != null && !sentCountryLabel.getValue( ).equals( countryGeocodesLabel ) )
{
request.getIdentity( ).getAttributes( ).remove( sentCountryLabel );
sentCountryLabel.setValue( countryGeocodesLabel );
request.getIdentity( ).getAttributes( ).add( sentCountryLabel );
}
// If no country label was sent, we add the attribute to the request with the Geocodes value, and same certifier as the sent code
if ( sentCountryLabel == null )
{
sentCountryLabel = new AttributeDto( );
sentCountryLabel.setKey( Constants.PARAM_BIRTH_COUNTRY );
sentCountryLabel.setValue( countryGeocodesLabel );
sentCountryLabel.setCertifier( sentCountryCode.getCertifier( ) );
sentCountryLabel.setCertificationDate( sentCountryCode.getCertificationDate( ) );
request.getIdentity( ).getAttributes( ).add( sentCountryLabel );
}
}
}
// No country code sent
else
{
// Country label was sent
if ( sentCountryLabel != null && StringUtils.isNotBlank( sentCountryLabel.getValue() ) )
{
Date birthdate;
try
{
final AttributeDto sentBirthdate = request.getIdentity( ).getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_DATE ) ).findFirst( ).orElse( null );
birthdate = sentBirthdate != null ? DateUtils.parseDate( sentBirthdate.getValue( ), "dd/MM/yyyy" ) : null;
}
catch( final ParseException e )
{
birthdate = null;
}
final List<Country> countries = new ArrayList<>();
if( birthdate != null )
{
countries.addAll(GeoCodesService.getInstance().getCountriesListByNameAndDate(sentCountryLabel.getValue(), birthdate));
} else
{
countries.addAll(GeoCodesService.getInstance().getCountriesListByName(sentCountryLabel.getValue()));
}
if (CollectionUtils.isEmpty(countries))
{
// Country doesn't exist in Geocodes for provided label : discard attribute and notify with an AttributeStatus
request.getIdentity().getAttributes().remove(sentCountryLabel);
final AttributeStatus attributeStatus = new AttributeStatus();
attributeStatus.setKey(sentCountryLabel.getKey());
attributeStatus.setStatus(AttributeChangeStatus.UNKNOWN_GEOCODES_LABEL);
attributeStatus.setMessageKey(Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_LABEL);
attrStatusList.add(attributeStatus);
} else if (countries.size() > 1)
{
// Multiple countries exist in Geocodes for provided label : discard attribute and notify with an AttributeStatus
request.getIdentity().getAttributes().remove(sentCountryLabel);
final AttributeStatus attributeStatus = new AttributeStatus();
attributeStatus.setKey(sentCountryLabel.getKey());
attributeStatus.setStatus(AttributeChangeStatus.MULTIPLE_GEOCODES_RESULTS_FOR_LABEL);
attributeStatus.setMessageKey(Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_GEOCODES_LABEL_MULTIPLE_RESULTS);
attrStatusList.add(attributeStatus);
} else
{
// One country exists in Geocodes for provided label : add the code attribute to the request with the Geocodes code value, and same
// certifier as the sent label
final String countryGeocodesCode = countries.get(0).getCode();
sentCountryCode = new AttributeDto();
sentCountryCode.setKey(Constants.PARAM_BIRTH_COUNTRY_CODE);
sentCountryCode.setValue(countryGeocodesCode);
sentCountryCode.setCertifier(sentCountryLabel.getCertifier());
sentCountryCode.setCertificationDate(sentCountryLabel.getCertificationDate());
request.getIdentity().getAttributes().add(sentCountryCode);
}
}
}
return attrStatusList;
}
private List<AttributeStatus> adjustFrenchCity( final IdentityChangeRequest request )
{
final List<AttributeStatus> attrStatusList = new ArrayList<>( );
AttributeDto sentCityCode = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE_CODE ) )
.findFirst( ).orElse( null );
AttributeDto sentCityLabel = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE ) )
.findFirst( ).orElse( null );
Date birthdate;
try
{
final AttributeDto sentBirthdate = request.getIdentity( ).getAttributes( ).stream( ).filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_DATE ) )
.findFirst( ).orElse( null );
birthdate = sentBirthdate != null ? DateUtils.parseDate( sentBirthdate.getValue( ), "dd/MM/yyyy" ) : null;
}
catch( final ParseException e )
{
birthdate = null;
}
// City code was sent
if ( sentCityCode != null && StringUtils.isNotBlank( sentCityCode.getValue( ) ) )
{
final List<City> cities = new ArrayList<>();
if(birthdate != null)
{
GeoCodesService.getInstance().getCityByDateAndCode( birthdate, sentCityCode.getValue( ) ).ifPresent( cities::add );
}
else
{
cities.addAll( GeoCodesService.getInstance( ).getCityByCode( sentCityCode.getValue( ) ) );
}
if (!cities.isEmpty())
{
if( cities.size( ) == 1 )
{
City city = cities.get(0);
if (city == null && (sentCityCode.getCertificationLevel() == null || sentCityCode.getCertificationLevel() < 600))
{
// city doesn't exist in Geocodes for provided code, and code is not FC certified : discard attribute and notify with an AttributeStatus
request.getIdentity().getAttributes().remove(sentCityCode);
final AttributeStatus attributeStatus = new AttributeStatus();
attributeStatus.setKey(sentCityCode.getKey());
attributeStatus.setStatus(AttributeChangeStatus.UNKNOWN_GEOCODES_CODE);
attributeStatus.setMessageKey(Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_CODE);
attrStatusList.add(attributeStatus);
} else
{
// city exists in Geocodes for provided code, or provided code is FC certified - adjust city label attribute if needed
final String cityGeocodesLabel = city != null ? city.getValueMinComplete() : "commune inconnue";
// If sent label is different than the Geocodes label, modify the request to override with the Geocodes value
if (sentCityLabel != null && !cityGeocodesLabel.equals(sentCityLabel.getValue()))
{
request.getIdentity().getAttributes().remove(sentCityLabel);
sentCityLabel.setValue(cityGeocodesLabel);
request.getIdentity().getAttributes().add(sentCityLabel);
}
// If no country label was sent, we add the attribute to the request with the Geocodes value, and same certifier as the sent code
if (sentCityLabel == null)
{
sentCityLabel = new AttributeDto();
sentCityLabel.setKey(Constants.PARAM_BIRTH_PLACE);
sentCityLabel.setValue(cityGeocodesLabel);
sentCityLabel.setCertifier(sentCityCode.getCertifier());
sentCityLabel.setCertificationDate(sentCityCode.getCertificationDate());
request.getIdentity().getAttributes().add(sentCityLabel);
}
}
} else
{
// The provided city code is linked to multiples cities
request.getIdentity().getAttributes().remove(sentCityCode);
final AttributeStatus attributeStatus = new AttributeStatus();
attributeStatus.setKey(sentCityCode.getKey());
attributeStatus.setStatus(AttributeChangeStatus.MULTIPLE_GEOCODES_RESULTS_FOR_CODE);
attributeStatus.setMessageKey(Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_GEOCODES_CODE_MULTIPLE_RESULTS);
attrStatusList.add(attributeStatus);
}
}
else
{
// city doesn't exist in Geocodes for provided code
request.getIdentity().getAttributes().remove(sentCityCode);
final AttributeStatus attributeStatus = new AttributeStatus();
attributeStatus.setKey(sentCityCode.getKey());
attributeStatus.setStatus(AttributeChangeStatus.UNKNOWN_GEOCODES_CODE);
attributeStatus.setMessageKey(Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_CODE);
attrStatusList.add(attributeStatus);
}
}
// No city code sent
else
{
// City label was sent
if ( sentCityLabel != null && StringUtils.isNotBlank( sentCityLabel.getValue( ) ) )
{
final List<City> cities = birthdate != null ? GeoCodesService.getInstance( ).getCitiesListByNameAndDate( sentCityLabel.getValue( ), birthdate )
: GeoCodesService.getInstance( ).getCitiesListByName( sentCityLabel.getValue( ) );
if ( CollectionUtils.isEmpty( cities ) )
{
// city doesn't exist in Geocodes for provided label : discard attribute and notify with an AttributeStatus
request.getIdentity( ).getAttributes( ).remove( sentCityLabel );
final AttributeStatus attributeStatus = new AttributeStatus( );
attributeStatus.setKey( sentCityLabel.getKey( ) );
attributeStatus.setStatus( AttributeChangeStatus.UNKNOWN_GEOCODES_LABEL );
attributeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_UNKNOWN_GEOCODES_LABEL );
attrStatusList.add( attributeStatus );
}
else
if ( cities.size( ) > 1 )
{
// Multiple cities exist in Geocodes for provided label : discard attribute and notify with an AttributeStatus
request.getIdentity( ).getAttributes( ).remove( sentCityLabel );
final AttributeStatus attributeStatus = new AttributeStatus( );
attributeStatus.setKey( sentCityLabel.getKey( ) );
attributeStatus.setStatus( AttributeChangeStatus.MULTIPLE_GEOCODES_RESULTS_FOR_LABEL );
attributeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_VALIDATION_ERROR_GEOCODES_LABEL_MULTIPLE_RESULTS );
attrStatusList.add( attributeStatus );
}
else
{
// One city exists in Geocodes for provided label - add city code attribute to request
final String countryGeocodesCode = cities.get( 0 ).getCode( );
// create city code attribute
sentCityCode = new AttributeDto( );
sentCityCode.setKey( Constants.PARAM_BIRTH_PLACE_CODE );
sentCityCode.setValue( countryGeocodesCode );
sentCityCode.setCertifier( sentCityLabel.getCertifier( ) );
sentCityCode.setCertificationDate( sentCityLabel.getCertificationDate( ) );
request.getIdentity( ).getAttributes( ).add( sentCityCode );
}
}
}
return attrStatusList;
}
private List<AttributeStatus> adjustForeignCity( final IdentityChangeRequest request, final IdentityDto existingIdentityToUpdate )
{
final List<AttributeStatus> attrStatusList = new ArrayList<>( );
final AttributeDto existingCityCode = existingIdentityToUpdate == null ? null :
existingIdentityToUpdate.getAttributes().stream().filter(a -> a.getKey().equals(Constants.PARAM_BIRTH_PLACE_CODE)).findFirst().orElse(null);
final AttributeDto sentCityCode = request.getIdentity( ).getAttributes( ).stream( )
.filter( a -> a.getKey( ).equals( Constants.PARAM_BIRTH_PLACE_CODE ) ).findFirst( ).orElse( null );
// City code is not supported for foreign countries
if ( existingCityCode != null )
{
// If a city code exists for the identity, put a blank one in the request to order a deletion
if ( sentCityCode != null )
{
// If a city code is sent in the request, discard it
request.getIdentity( ).getAttributes( ).remove( sentCityCode );
}
final AttributeDto cityCodeToDelete = new AttributeDto();
cityCodeToDelete.setKey( Constants.PARAM_BIRTH_PLACE_CODE );
cityCodeToDelete.setValue( StringUtils.EMPTY );
cityCodeToDelete.setCertifier( existingCityCode.getCertifier( ) );
cityCodeToDelete.setCertificationDate( existingCityCode.getCertificationDate( ) );
cityCodeToDelete.setCertificationLevel( existingCityCode.getCertificationLevel( ) );
cityCodeToDelete.setType( existingCityCode.getType( ) );
cityCodeToDelete.setLastUpdateClientCode( existingCityCode.getLastUpdateClientCode( ) );
cityCodeToDelete.setLastUpdateDate( existingCityCode.getLastUpdateDate( ) );
request.getIdentity( ).getAttributes( ).add( cityCodeToDelete );
}
else
{
// If the identity doesn't have a city code already
if ( sentCityCode != null && StringUtils.isNotBlank( sentCityCode.getValue( ) ) )
{
// If a non-blank city code is sent in the request, discard it and mark it as "not created"
request.getIdentity( ).getAttributes( ).remove( sentCityCode );
final AttributeStatus cityCodeStatus = new AttributeStatus( );
cityCodeStatus.setStatus( AttributeChangeStatus.NOT_CREATED );
cityCodeStatus.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_NOT_CREATED );
cityCodeStatus.setKey( Constants.PARAM_BIRTH_PLACE_CODE );
attrStatusList.add( cityCodeStatus );
}
}
// No adjuments are made on the city label for foreign country : if it was sent, it will be created or updated like this
return attrStatusList;
}
}