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.attribute;
35  
36  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeChangeStatus;
37  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.AttributeStatus;
38  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.common.IdentityDto;
39  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.crud.IdentityChangeRequest;
40  import fr.paris.lutece.plugins.identitystore.v3.web.rs.dto.merge.IdentityMergeRequest;
41  import fr.paris.lutece.plugins.identitystore.v3.web.rs.util.Constants;
42  import org.apache.commons.lang3.StringUtils;
43  
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.stream.Collectors;
49  
50  /**
51   * Service class used to format attribute values in requests
52   */
53  public class IdentityAttributeFormatterService
54  {
55  
56      private static IdentityAttributeFormatterService _instance;
57      private static final List<String> PHONE_ATTR_KEYS = Arrays.asList( "mobile_phone", "fixed_phone" );
58      private static final List<String> DATE_ATTR_KEYS = Collections.singletonList( "birthdate" );
59      private static final List<String> FIRSTNAME_ATTR_KEYS = Collections.singletonList( "first_name" );
60      private static final List<String> UPPERCASE_ATTR_KEYS = Arrays.asList( "birthcountry", "family_name", "preferred_username" );
61      private static final List<String> LOWERCASE_ATTR_KEYS = Arrays.asList( "login", "email" );
62  
63      public static IdentityAttributeFormatterService instance( )
64      {
65          if ( _instance == null )
66          {
67              _instance = new IdentityAttributeFormatterService( );
68          }
69          return _instance;
70      }
71  
72      /**
73       * Formats attribute values in the Identity contained in the provided request.
74       * 
75       * @see IdentityAttributeFormatterService#formatIdentityAttributeValues(IdentityDto)
76       * @param request
77       *            the identity change request
78       */
79      public List<AttributeStatus> formatIdentityChangeRequestAttributeValues( final IdentityChangeRequest request )
80      {
81          final IdentityDto identity = request.getIdentity( );
82          final List<AttributeStatus> statuses = this.formatIdentityAttributeValues( identity );
83          request.setIdentity( identity );
84          return statuses;
85      }
86  
87      /**
88       * Formats attribute values in the Identity contained in the provided request.
89       * 
90       * @see IdentityAttributeFormatterService#formatIdentityAttributeValues(IdentityDto)
91       * @param request
92       *            the identity merge request
93       */
94      public List<AttributeStatus> formatIdentityMergeRequestAttributeValues( final IdentityMergeRequest request )
95      {
96          final List<AttributeStatus> statuses = new ArrayList<>( );
97          final IdentityDto identity = request.getIdentity( );
98          if ( identity != null )
99          {
100             statuses.addAll( this.formatIdentityAttributeValues( identity ) );
101             request.setIdentity( identity );
102         }
103         return statuses;
104     }
105 
106     /**
107      * Formats all attributes stored in the provided identity :
108      * <ul>
109      * <li>Remove leading and trailing spaces</li>
110      * <li>Replace all blank characters by an actual space</li>
111      * <li>Replace space successions with a single space</li>
112      * <li>For phone number attributes :
113      * <ul>
114      * <li>Remove all spaces, dots, dashes and parenthesis</li>
115      * <li>Replace leading indicative part (0033 or +33) by a single zero</li>
116      * </ul>
117      * </li>
118      * <li>For date attributes :
119      * <ul>
120      * <li>Put a leading zero in day and month parts if they contain only one character</li>
121      * </ul>
122      * </li>
123      * <li>For first name attributes :
124      * <ul>
125      * <li>Replace comas (,) by a single whitespace</li>
126      * <li>Force the first character of each group (space-separated) to be uppercase, the rest is forced to lowercase</li>
127      * </ul>
128      * </li>
129      * <li>For country label, family name and prefered name attributes :
130      * <ul>
131      * <li>force to uppercase</li>
132      * </ul>
133      * </li>
134      * <li>For login and email attributes :
135      * <ul>
136      * <li>force to lowercase</li>
137      * </ul>
138      * </li>
139      * </ul>
140      *
141      * @param identity
142      *            identity containing attributes to format
143      * @return FORMATTED_VALUE statuses for attributes whose value has changed after the formatting.
144      */
145     private List<AttributeStatus> formatIdentityAttributeValues( final IdentityDto identity )
146     {
147         final List<AttributeStatus> statuses = new ArrayList<>( );
148         identity.getAttributes( ).stream( ).filter( attributeDto -> StringUtils.isNotBlank( attributeDto.getValue( ) ) ).forEach( attribute -> {
149             // Suppression espaces avant et après, et uniformisation des espacements (tab, space, nbsp, successions d'espaces, ...) en les remplaçant tous par
150             // un espace
151             String formattedValue = attribute.getValue( ).trim( ).replaceAll( "\\s+", " " );
152 
153             if ( PHONE_ATTR_KEYS.contains( attribute.getKey( ) ) )
154             {
155                 formattedValue = formatPhoneValue( formattedValue );
156             }
157             if ( DATE_ATTR_KEYS.contains( attribute.getKey( ) ) )
158             {
159                 formattedValue = formatDateValue( formattedValue );
160             }
161             if ( FIRSTNAME_ATTR_KEYS.contains( attribute.getKey( ) ) )
162             {
163                 formattedValue = formatFirstnameValue( formattedValue );
164             }
165             if ( UPPERCASE_ATTR_KEYS.contains( attribute.getKey( ) ) )
166             {
167                 formattedValue = StringUtils.upperCase( formattedValue );
168             }
169             if ( LOWERCASE_ATTR_KEYS.contains( attribute.getKey( ) ) )
170             {
171                 formattedValue = StringUtils.lowerCase( formattedValue );
172             }
173 
174             // Si la valeur a été modifiée, on renvoie un status
175             if ( !formattedValue.equals( attribute.getValue( ) ) )
176             {
177                 statuses.add( buildAttributeValueFormattedStatus( attribute.getKey( ), attribute.getValue( ), formattedValue ) );
178             }
179             attribute.setValue( formattedValue );
180         } );
181         return statuses;
182     }
183 
184     /**
185      * <ul>
186      * <li>Remove all spaces, dots, dashes and parenthesis</li>
187      * <li>Replace leading indicative part (0033 or +33) by a single zero</li>
188      * </ul>
189      * 
190      * @param value
191      *            the value to format
192      * @return the formatted value
193      */
194     private String formatPhoneValue( final String value )
195     {
196         // Suppression des espaces, points, tirets, et parenthèses
197         String formattedValue = value.replaceAll( "\\s", "" ).replace( ".", "" ).replace( "-", "" ).replace( "(", "" ).replace( ")", "" );
198         // Remplacement de l'indicatif (0033 ou +33) par un 0
199         formattedValue = formattedValue.replaceAll( "^(0{2}|\\+)3{2}", "0" );
200 
201         return formattedValue;
202     }
203 
204     /**
205      * Put a leading zero in day and month parts if they contain only one character
206      * 
207      * @param value
208      *            the value to format
209      * @return the formatted value
210      */
211     public String formatDateValue( final String value )
212     {
213         final StringBuilder sb = new StringBuilder( );
214         final String [ ] splittedDate = value.split( "/" );
215         if ( splittedDate.length == 3 )
216         {
217             final String day = splittedDate [0];
218             if ( day.length( ) == 1 )
219             {
220                 sb.append( "0" );
221             }
222             sb.append( day ).append( "/" );
223 
224             final String month = splittedDate [1];
225             if ( month.length( ) == 1 )
226             {
227                 sb.append( "0" );
228             }
229             sb.append( month ).append( "/" ).append( splittedDate [2] );
230 
231             return sb.toString( );
232         }
233         else
234         {
235             return value;
236         }
237     }
238 
239     /**
240      * <ul>
241      * <li>Replace comas (,) by a single whitespace</li>
242      * <li>Force the first character of each group (space-separated) to be uppercase, the rest is forced to lowercase</li>
243      * </ul>
244      * 
245      * @param value
246      *            the value to format
247      * @return the formatted value
248      */
249     private String formatFirstnameValue( final String value )
250     {
251         if ( StringUtils.isBlank( value ) )
252         {
253             return value;
254         }
255         return Arrays.stream( value.replace( ",", " " ).trim( ).split( " " ) ).filter( StringUtils::isNotBlank ).map( String::trim )
256                 .map( firstname -> firstname.substring( 0, 1 ).toUpperCase( ) + firstname.substring( 1 ).toLowerCase( ) ).collect( Collectors.joining( " " ) );
257     }
258 
259     /**
260      * Build attribute value formatted status
261      * 
262      * @param attrStrKey
263      *            the attribute key
264      * @return the status
265      */
266     public AttributeStatus buildAttributeValueFormattedStatus( final String attrStrKey, final String oldValue, final String newValue )
267     {
268         final AttributeStatus status = new AttributeStatus( );
269         status.setKey( attrStrKey );
270         status.setStatus( AttributeChangeStatus.FORMATTED_VALUE );
271         status.setMessage( "[" + oldValue + "] -> [" + newValue + "]" );
272         status.setMessageKey( Constants.PROPERTY_ATTRIBUTE_STATUS_FORMATTED_VALUE );
273         return status;
274     }
275 
276 }