View Javadoc
1   /*
2    * Copyright (c) 2002-2017, Mairie de 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.modules.cnicertifier.service;
35  
36  import com.fasterxml.jackson.databind.JsonNode;
37  import com.fasterxml.jackson.databind.ObjectMapper;
38  import fr.paris.lutece.plugins.identitystore.modules.cnicertifier.business.CNI;
39  import fr.paris.lutece.plugins.identitystore.modules.cnicertifier.business.ScanOutput;
40  import fr.paris.lutece.portal.service.i18n.I18nService;
41  import fr.paris.lutece.portal.service.util.AppPropertiesService;
42  import fr.paris.lutece.util.httpaccess.HttpAccess;
43  import fr.paris.lutece.util.httpaccess.HttpAccessException;
44  import java.io.IOException;
45  import java.util.Locale;
46  import java.util.Map;
47  import org.apache.commons.fileupload.FileItem;
48  
49  /**
50   * ScannerService
51   */
52  public class ScannerService
53  {
54      /** The extraction of the document within the image failed (extract_document() function failed). */
55      private static final String ERROR_DOCUMENT_EXTRACTION_FAILED = "DOCUMENT_EXTRACTION_FAILED";
56  
57      /** The improvement of the extracted image failed (improve_image() function failed). */
58      private static final String ERROR_IMAGE_IMPROVEMENT_FAILED = "IMAGE_IMPROVEMENT_FAILED";
59  
60      /** The image provided is too small, its smallest side is lower than 900 pixels. The image must be at least 900x900 pixels. */
61      private static final String ERROR_IMG_SIZE_TOO_SMALL = "IMG_SIZE_TOO_SMALL";
62  
63      /** OCR data and MRZ data don't match (same_ocr_mrz() function returned False). */
64      private static final String ERROR_INCONSISTENT_OCR_MRZ = "INCONSISTENT_OCR_MRZ";
65  
66      /** The checksum of the date of birth extracted from the MRZ (characters from 28 to 33 of the second line) is not valid. */
67      private static final String ERROR_INVALID_BIRTHDATE_CHECKSUM = "INVALID_BIRTHDATE_CHECKSUM";
68  
69      /** The checksum of the first 12 characters of the second line of the MRZ is not valid. */
70      private static final String ERROR_INVALID_EMIT_CHECKSUM = "INVALID_EMIT_CHECKSUM";
71  
72      /** The type of the file provided is neither jpg (JPEG) nor png (PNG) nor pdf (PDF), and is therefore not supported. */
73      private static final String ERROR_INVALID_FILE_TYPE = "INVALID_FILE_TYPE";
74  
75      /** The checksum of the first line and the first 35 characters of the second line is not valid. */
76      private static final String ERROR_INVALID_GLOBAL_CHECKSUM = "INVALID_GLOBAL_CHECKSUM";
77  
78      /** The first line (line 0) of the MRZ is not 36 characters long as expected. */
79      private static final String ERROR_INVALID_LINE0_LENGTH = "INVALID_LINE0_LENGTH";
80  
81      /** The second line (line 1) of the MRZ is not 36 characters long as expected. */
82      private static final String ERROR_INVALID_LINE1_LENGTH = "INVALID_LINE1_LENGTH";
83  
84      /** The first 2 characters of the first line of the MRZ are not ID as expected. */
85      private static final String ERROR_INVALID_MRZ_ID = "INVALID_MRZ_ID";
86  
87      /** The MRZ data extracted does not contain 2 lines as expected. */
88      private static final String ERROR_INVALID_MRZ_LINES_COUNT = "INVALID_MRZ_LINES_COUNT";
89  
90      /** The sex character extracted from the MRZ is neither M (male) nor F (female). */
91      private static final String ERROR_INVALID_MRZ_SEX = "INVALID_MRZ_SEX";
92  
93      /** The image file is missing in the image field of the HTTP POST request. */
94      private static final String ERROR_MISSING_IMAGE_FILE = "MISSING_IMAGE_FILE";
95  
96      /** The extraction of the MRZ failed (cni_mrz_extract() function failed). */
97      private static final String ERROR_MRZ_EXTRACTION_FAILED = "MRZ_EXTRACTION_FAILED";
98  
99      /** The location of the different zones in the document failed (cni_locate_zone() function failed). */
100     private static final String ERROR_ZONES_LOCATION_FAILED = "ZONES_LOCATION_FAILED";
101 
102     private static final String PROPERTY_SCANNER_URL = "identitystore-cnicertifier.scannerUrl";
103 
104     private static final String MESSAGE_IMG_SIZE_TOO_SMALL= "module.identitystore.cnicertifier.message.imageSizeTooSmall";
105     private static final String MESSAGE_CHECK_FAILED = "module.identitystore.cnicertifier.message.checkFailed";
106     private static final String MESSAGE_SCAN_FAILED = "module.identitystore.cnicertifier.message.scanFailed";
107     private static final String MESSAGE_FILE_MISSING = "module.identitystore.cnicertifier.message.fileMissing";
108     private static final String MESSAGE_INVALID_FILE_TYPE = "module.identitystore.cnicertifier.message.invalidFileType";
109     
110     private static ObjectMapper _mapper = new ObjectMapper( );
111     private static ScannerResponseStatusValidator _validator = new ScannerResponseStatusValidator();
112 
113     /**
114      * Scan the CNI
115      *
116      * @param mapFileItems
117      *            Files
118      * @return The CNI
119      * @throws ScannerException
120      * @throws HttpAccessException
121      */
122     public static CNI scan( Map<String, FileItem> mapFileItems ) throws ScannerException, HttpAccessException
123     {
124         HttpAccess client = new HttpAccess( _validator );
125         
126         String strURL = AppPropertiesService.getProperty( PROPERTY_SCANNER_URL );
127         String strResponse = client.doPostMultiPart( strURL, null, mapFileItems );
128         CNI cni = parse( strResponse );
129         return cni;
130     }
131 
132     /**
133      * Parse the response of the scanner server
134      * 
135      * @param strJSON
136      *            The response as JSON
137      * @return The CNI object
138      * @throws ScannerException
139      *             A scanner exception
140      */
141     public static CNI parse( String strJSON ) throws ScannerException
142     {
143         CNI cni = null;
144 
145         try
146         {
147             JsonNode nodeRoot = _mapper.readTree( strJSON );
148             JsonNode nodeData = nodeRoot.get( "data" );
149             
150             if( nodeData != null )
151             {
152                 String strDataJSON = nodeData.toString( );
153                 ScanOutput scan = _mapper.readValue( strDataJSON, ScanOutput.class );
154                 cni = new CNI( scan );
155             }
156             else
157             {
158                 String strCode = getField( nodeRoot , "code" );
159                 String strException = getField( nodeRoot , "exception" );
160                 String strMessage = getField( nodeRoot , "message" );
161                 String strUserMessage = handleError( strCode );
162                 throw new ScannerException( strMessage , strCode , strException , strUserMessage );
163             }
164         }
165         catch( IOException ex )
166         {
167             throw new ScannerException( ex.getMessage( ) );
168         }
169         return cni;
170     }
171 
172     /**
173      * Handle an error code to produce an user message
174      * @param strCode The error code
175      * @return The user message
176      */
177     private static String handleError( String strCode )
178     {
179         if( ERROR_IMG_SIZE_TOO_SMALL.equals( strCode ))
180         {
181             return I18nService.getLocalizedString( MESSAGE_IMG_SIZE_TOO_SMALL, Locale.FRENCH );
182         }
183         if(     ERROR_INCONSISTENT_OCR_MRZ.equals( strCode) || 
184                 ERROR_INVALID_BIRTHDATE_CHECKSUM.equals( strCode ) ||
185                 ERROR_INVALID_EMIT_CHECKSUM.equals( strCode ) ||
186                 ERROR_INVALID_GLOBAL_CHECKSUM.equals( strCode ) ||
187                 ERROR_INVALID_MRZ_ID.equals( strCode ) ||
188                 ERROR_INVALID_MRZ_LINES_COUNT.equals( strCode ) ||
189                 ERROR_INVALID_MRZ_SEX.equals( strCode ))
190         {
191             return I18nService.getLocalizedString( MESSAGE_CHECK_FAILED, Locale.FRENCH );
192         }    
193         
194         if( ERROR_MRZ_EXTRACTION_FAILED.equals( strCode ) ||
195                 ERROR_DOCUMENT_EXTRACTION_FAILED.equals( strCode ) ||
196                 ERROR_MRZ_EXTRACTION_FAILED.equals( strCode ) ||
197                 ERROR_IMAGE_IMPROVEMENT_FAILED.equals( strCode ) ||
198                 ERROR_INVALID_LINE0_LENGTH.equals( strCode ) ||
199                 ERROR_INVALID_LINE1_LENGTH.equals( strCode ) ||
200                 ERROR_ZONES_LOCATION_FAILED.equals( strCode ))
201         {
202             return I18nService.getLocalizedString( MESSAGE_SCAN_FAILED, Locale.FRENCH );
203         }
204         
205         if( ERROR_INVALID_FILE_TYPE.equals( strCode ) )
206         {
207             return I18nService.getLocalizedString( MESSAGE_INVALID_FILE_TYPE, Locale.FRENCH );
208         }
209         
210         if( ERROR_MISSING_IMAGE_FILE.equals( strCode ))
211         {
212             return I18nService.getLocalizedString( MESSAGE_FILE_MISSING, Locale.FRENCH );
213         }
214         
215         return I18nService.getLocalizedString( MESSAGE_SCAN_FAILED, Locale.FRENCH );
216     }
217 
218     /**
219      * Gets field content avoiding null values for missing fields
220      * @param nodeRoot The root
221      * @param strField The field name
222      * @return The field content
223      */
224     private static String getField( JsonNode nodeRoot, String strField  )
225     {
226         JsonNode node = nodeRoot.get( strField );
227         return ( node != null ) ? node.asText() : "";
228     }
229 }