ScannerService.java
/*
* Copyright (c) 2002-2017, Mairie de 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.modules.cnicertifier.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.paris.lutece.plugins.identitystore.modules.cnicertifier.business.CNI;
import fr.paris.lutece.plugins.identitystore.modules.cnicertifier.business.ScanOutput;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.httpaccess.HttpAccess;
import fr.paris.lutece.util.httpaccess.HttpAccessException;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.fileupload.FileItem;
/**
* ScannerService
*/
public class ScannerService
{
/** The extraction of the document within the image failed (extract_document() function failed). */
private static final String ERROR_DOCUMENT_EXTRACTION_FAILED = "DOCUMENT_EXTRACTION_FAILED";
/** The improvement of the extracted image failed (improve_image() function failed). */
private static final String ERROR_IMAGE_IMPROVEMENT_FAILED = "IMAGE_IMPROVEMENT_FAILED";
/** The image provided is too small, its smallest side is lower than 900 pixels. The image must be at least 900x900 pixels. */
private static final String ERROR_IMG_SIZE_TOO_SMALL = "IMG_SIZE_TOO_SMALL";
/** OCR data and MRZ data don't match (same_ocr_mrz() function returned False). */
private static final String ERROR_INCONSISTENT_OCR_MRZ = "INCONSISTENT_OCR_MRZ";
/** The checksum of the date of birth extracted from the MRZ (characters from 28 to 33 of the second line) is not valid. */
private static final String ERROR_INVALID_BIRTHDATE_CHECKSUM = "INVALID_BIRTHDATE_CHECKSUM";
/** The checksum of the first 12 characters of the second line of the MRZ is not valid. */
private static final String ERROR_INVALID_EMIT_CHECKSUM = "INVALID_EMIT_CHECKSUM";
/** The type of the file provided is neither jpg (JPEG) nor png (PNG) nor pdf (PDF), and is therefore not supported. */
private static final String ERROR_INVALID_FILE_TYPE = "INVALID_FILE_TYPE";
/** The checksum of the first line and the first 35 characters of the second line is not valid. */
private static final String ERROR_INVALID_GLOBAL_CHECKSUM = "INVALID_GLOBAL_CHECKSUM";
/** The first line (line 0) of the MRZ is not 36 characters long as expected. */
private static final String ERROR_INVALID_LINE0_LENGTH = "INVALID_LINE0_LENGTH";
/** The second line (line 1) of the MRZ is not 36 characters long as expected. */
private static final String ERROR_INVALID_LINE1_LENGTH = "INVALID_LINE1_LENGTH";
/** The first 2 characters of the first line of the MRZ are not ID as expected. */
private static final String ERROR_INVALID_MRZ_ID = "INVALID_MRZ_ID";
/** The MRZ data extracted does not contain 2 lines as expected. */
private static final String ERROR_INVALID_MRZ_LINES_COUNT = "INVALID_MRZ_LINES_COUNT";
/** The sex character extracted from the MRZ is neither M (male) nor F (female). */
private static final String ERROR_INVALID_MRZ_SEX = "INVALID_MRZ_SEX";
/** The image file is missing in the image field of the HTTP POST request. */
private static final String ERROR_MISSING_IMAGE_FILE = "MISSING_IMAGE_FILE";
/** The extraction of the MRZ failed (cni_mrz_extract() function failed). */
private static final String ERROR_MRZ_EXTRACTION_FAILED = "MRZ_EXTRACTION_FAILED";
/** The location of the different zones in the document failed (cni_locate_zone() function failed). */
private static final String ERROR_ZONES_LOCATION_FAILED = "ZONES_LOCATION_FAILED";
private static final String PROPERTY_SCANNER_URL = "identitystore-cnicertifier.scannerUrl";
private static final String MESSAGE_IMG_SIZE_TOO_SMALL= "module.identitystore.cnicertifier.message.imageSizeTooSmall";
private static final String MESSAGE_CHECK_FAILED = "module.identitystore.cnicertifier.message.checkFailed";
private static final String MESSAGE_SCAN_FAILED = "module.identitystore.cnicertifier.message.scanFailed";
private static final String MESSAGE_FILE_MISSING = "module.identitystore.cnicertifier.message.fileMissing";
private static final String MESSAGE_INVALID_FILE_TYPE = "module.identitystore.cnicertifier.message.invalidFileType";
private static ObjectMapper _mapper = new ObjectMapper( );
private static ScannerResponseStatusValidator _validator = new ScannerResponseStatusValidator();
/**
* Scan the CNI
*
* @param mapFileItems
* Files
* @return The CNI
* @throws ScannerException
* @throws HttpAccessException
*/
public static CNI scan( Map<String, FileItem> mapFileItems ) throws ScannerException, HttpAccessException
{
HttpAccess client = new HttpAccess( _validator );
String strURL = AppPropertiesService.getProperty( PROPERTY_SCANNER_URL );
String strResponse = client.doPostMultiPart( strURL, null, mapFileItems );
CNI cni = parse( strResponse );
return cni;
}
/**
* Parse the response of the scanner server
*
* @param strJSON
* The response as JSON
* @return The CNI object
* @throws ScannerException
* A scanner exception
*/
public static CNI parse( String strJSON ) throws ScannerException
{
CNI cni = null;
try
{
JsonNode nodeRoot = _mapper.readTree( strJSON );
JsonNode nodeData = nodeRoot.get( "data" );
if( nodeData != null )
{
String strDataJSON = nodeData.toString( );
ScanOutput scan = _mapper.readValue( strDataJSON, ScanOutput.class );
cni = new CNI( scan );
}
else
{
String strCode = getField( nodeRoot , "code" );
String strException = getField( nodeRoot , "exception" );
String strMessage = getField( nodeRoot , "message" );
String strUserMessage = handleError( strCode );
throw new ScannerException( strMessage , strCode , strException , strUserMessage );
}
}
catch( IOException ex )
{
throw new ScannerException( ex.getMessage( ) );
}
return cni;
}
/**
* Handle an error code to produce an user message
* @param strCode The error code
* @return The user message
*/
private static String handleError( String strCode )
{
if( ERROR_IMG_SIZE_TOO_SMALL.equals( strCode ))
{
return I18nService.getLocalizedString( MESSAGE_IMG_SIZE_TOO_SMALL, Locale.FRENCH );
}
if( ERROR_INCONSISTENT_OCR_MRZ.equals( strCode) ||
ERROR_INVALID_BIRTHDATE_CHECKSUM.equals( strCode ) ||
ERROR_INVALID_EMIT_CHECKSUM.equals( strCode ) ||
ERROR_INVALID_GLOBAL_CHECKSUM.equals( strCode ) ||
ERROR_INVALID_MRZ_ID.equals( strCode ) ||
ERROR_INVALID_MRZ_LINES_COUNT.equals( strCode ) ||
ERROR_INVALID_MRZ_SEX.equals( strCode ))
{
return I18nService.getLocalizedString( MESSAGE_CHECK_FAILED, Locale.FRENCH );
}
if( ERROR_MRZ_EXTRACTION_FAILED.equals( strCode ) ||
ERROR_DOCUMENT_EXTRACTION_FAILED.equals( strCode ) ||
ERROR_MRZ_EXTRACTION_FAILED.equals( strCode ) ||
ERROR_IMAGE_IMPROVEMENT_FAILED.equals( strCode ) ||
ERROR_INVALID_LINE0_LENGTH.equals( strCode ) ||
ERROR_INVALID_LINE1_LENGTH.equals( strCode ) ||
ERROR_ZONES_LOCATION_FAILED.equals( strCode ))
{
return I18nService.getLocalizedString( MESSAGE_SCAN_FAILED, Locale.FRENCH );
}
if( ERROR_INVALID_FILE_TYPE.equals( strCode ) )
{
return I18nService.getLocalizedString( MESSAGE_INVALID_FILE_TYPE, Locale.FRENCH );
}
if( ERROR_MISSING_IMAGE_FILE.equals( strCode ))
{
return I18nService.getLocalizedString( MESSAGE_FILE_MISSING, Locale.FRENCH );
}
return I18nService.getLocalizedString( MESSAGE_SCAN_FAILED, Locale.FRENCH );
}
/**
* Gets field content avoiding null values for missing fields
* @param nodeRoot The root
* @param strField The field name
* @return The field content
*/
private static String getField( JsonNode nodeRoot, String strField )
{
JsonNode node = nodeRoot.get( strField );
return ( node != null ) ? node.asText() : "";
}
}