GRUSupplyRestService.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.grusupply.web.rs;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.paris.lutece.plugins.grubusiness.business.customer.Customer;
import fr.paris.lutece.plugins.grubusiness.business.demand.Demand;
import fr.paris.lutece.plugins.grubusiness.business.demand.DemandService;
import fr.paris.lutece.plugins.grubusiness.business.notification.Event;
import fr.paris.lutece.plugins.grubusiness.business.notification.Notification;
import fr.paris.lutece.plugins.grubusiness.business.notification.NotificationEvent;
import fr.paris.lutece.plugins.grubusiness.business.web.rs.EnumGenericStatus;
import fr.paris.lutece.plugins.grubusiness.service.notification.NotificationException;
import fr.paris.lutece.plugins.grustoragedb.business.DemandTypeHome;
import fr.paris.lutece.plugins.grustoragedb.business.NotificationContentHome;
import fr.paris.lutece.plugins.grustoragedb.business.StatusHome;
import fr.paris.lutece.plugins.grusupply.business.StatusMessage;
import fr.paris.lutece.plugins.grusupply.constant.GruSupplyConstants;
import fr.paris.lutece.plugins.grusupply.service.CustomerProvider;
import fr.paris.lutece.plugins.grusupply.service.NotificationService;
import fr.paris.lutece.plugins.identitystore.web.exception.IdentityNotFoundException;
import fr.paris.lutece.plugins.rest.service.RestConstants;
import fr.paris.lutece.portal.service.util.AppLogService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
@Path( RestConstants.BASE_PATH + GruSupplyConstants.PLUGIN_NAME )
public class GRUSupplyRestService
{
// Bean names
private static final String BEAN_STORAGE_SERVICE = "grusupply.storageService";
// Messages
private static final String WARNING_DEMAND_ID_MANDATORY = "Notification Demand_id field is mandatory";
private static final String WARNING_DEMAND_TYPE_ID_MANDATORY = "Notification Demand_type_id field is mandatory";
private static final String WARNING_CUSTOMER_ID_MANDATORY = "Notification connection_id field is mandatory";
private static final String MESSAGE_MISSING_MANDATORY_FIELD = "Missing value";
private static final String MESSAGE_MISSING_DEMAND_ID = "Demand Id and Demand type Id are mandatory";
private static final String MESSAGE_MISSING_USER_ID = "User connection id is mandatory";
private static final String MESSAGE_INCORRECT_DEMAND_ID = "Demand Type Id not found";
// Other constants
private static final String RESPONSE_OK = "{ \"acknowledge\" : { \"status\": \"received\" } }";
private static final String TYPE_DEMAND = "DEMAND";
private static final String TYPE_NOTIFICATION = "DEMAND";
private static final String STATUS_WARNING = "WARNING";
private static final String STATUS_ERROR = "ERROR";
private static final String STATUS_FAILED = "FAILED";
private static final String TYPE_GUICHET = "GUICHET";
// instance variable
@Inject
@Named( BEAN_STORAGE_SERVICE )
private DemandService _demandService;
/**
* Web Service methode which permit to store the notification flow into a data store
*
* @param strJson
* The JSON flow
* @return The response
*/
@POST
@Path( "notification" )
@Consumes( MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_JSON )
public Response notification( String strJson )
{
List<StatusMessage> warnings = new ArrayList<>( );
try
{
// Format from JSON
ObjectMapper mapper = new ObjectMapper( );
mapper.configure( DeserializationFeature.UNWRAP_ROOT_VALUE, true );
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
Notification notification = mapper.readValue( strJson, Notification.class );
AppLogService.debug( "grusupply / notification - Received strJson : " + strJson );
Customer customerEncrypted = notification.getDemand( ).getCustomer( );
if ( CustomerProvider.instance( ).hasIdentityService( ) )
{
Customer customerDecrypted = CustomerProvider.instance( ).decrypt( customerEncrypted, notification.getDemand( ) );
if ( customerDecrypted != null && StringUtils.isNotEmpty( customerDecrypted.getConnectionId( ) )
&& StringUtils.isEmpty( customerDecrypted.getId( ) ) )
{
try
{
Customer customerTmp = CustomerProvider.instance( ).get( customerDecrypted.getConnectionId( ), StringUtils.EMPTY );
customerDecrypted.setId( customerTmp.getId( ) );
}
catch ( IdentityNotFoundException e )
{
// customer not found in IDS
AppLogService.debug( "Customer not found with connection_id : " + customerDecrypted.getConnectionId( ) );
customerDecrypted = null;
}
}
if ( customerDecrypted == null )
{
customerDecrypted = new Customer( );
customerDecrypted.setConnectionId( StringUtils.EMPTY );
customerDecrypted.setId( StringUtils.EMPTY );
notification.getDemand().setCustomer( customerDecrypted );
}
notification.getDemand( ).setCustomer( customerDecrypted );
}
else
{
notification.getDemand( ).setCustomer( customerEncrypted );
}
// store any notification whatever its content
store( notification );
// notification should be associated to a demand id
if ( StringUtils.isBlank( notification.getDemand( ).getId( ) ) )
{
StatusMessage msg = new StatusMessage( TYPE_DEMAND, STATUS_WARNING, MESSAGE_MISSING_MANDATORY_FIELD, WARNING_DEMAND_ID_MANDATORY );
warnings.add( msg );
}
// notification should be associated to a demand type id
if ( StringUtils.isBlank( notification.getDemand( ).getTypeId( ) ) )
{
StatusMessage msg = new StatusMessage( TYPE_DEMAND, STATUS_WARNING, MESSAGE_MISSING_MANDATORY_FIELD, WARNING_DEMAND_TYPE_ID_MANDATORY );
warnings.add( msg );
}
// notification should be associated to a customer id
if ( notification.getDemand( ).getCustomer( ) != null
&& StringUtils.isBlank( notification.getDemand( ).getCustomer( ).getConnectionId( ) ) )
{
StatusMessage msg = new StatusMessage( TYPE_DEMAND, STATUS_WARNING, MESSAGE_MISSING_MANDATORY_FIELD, WARNING_CUSTOMER_ID_MANDATORY );
warnings.add( msg );
}
// Notify user and crm if a bean NotificationService is instantiated
NotificationService notificationService = NotificationService.instance( );
if ( notificationService != null )
{
AppLogService.info( "GRUSUPPLY - Process Notification" + notification.getId( ) );
NotificationService.instance().process( notification );
}
//Create NotificationContent
NotificationContentHome.create( notification );
//Notification Event
controlNotificationEvent( notification );
}
catch( JsonParseException ex )
{
return fail( ex, Response.Status.BAD_REQUEST );
}
catch( JsonMappingException | NullPointerException | NotificationException ex )
{
return fail( ex, Response.Status.BAD_REQUEST );
}
catch( IOException ex )
{
return fail( ex, Response.Status.INTERNAL_SERVER_ERROR );
}
catch( Exception ex )
{
return fail( ex, Response.Status.INTERNAL_SERVER_ERROR );
}
if ( warnings.isEmpty( ) )
{
return success( );
}
else
{
return successWithWarnings( warnings );
}
}
/**
* Values and store the NotificationEvent object if failure
* @param notification
* @param strMessage
*/
private void controlNotificationEvent ( Notification notification )
{
if ( notification.getMyDashboardNotification( ) != null )
{
String strMessage = checkNotification( notification );
if( StringUtils.isNotEmpty( strMessage ))
{
NotificationEvent notificationEvent = new NotificationEvent( );
Event event = new Event( );
event.setMessage( strMessage );
event.setStatus( STATUS_FAILED );
event.setType( TYPE_GUICHET );
event.setEventDate( notification.getDate( ) );
notificationEvent.setEvent( event );
notificationEvent.setMsgId( StringUtils.EMPTY );
notificationEvent.setDemand( notification.getDemand( ) );
notificationEvent.setNotificationDate( notification.getDate( ) );
store( notificationEvent );
}
}
}
/**
* Check notification
* @param notif
* @return The message error
*/
private String checkNotification( Notification notif )
{
// check if connection id is present
if ( notif.getDemand( ) == null || notif.getDemand( ).getCustomer( ) == null || StringUtils.isBlank( notif.getDemand( ).getCustomer( ).getConnectionId( ) ) )
{
return generateErrorMessage( notif, Response.Status.PRECONDITION_FAILED, MESSAGE_MISSING_USER_ID );
}
// check if Demand remote id and demand type id are present
if ( StringUtils.isBlank( notif.getDemand( ).getId( ) ) || StringUtils.isBlank( notif.getDemand( ).getTypeId( ) ) )
{
return generateErrorMessage( notif, Response.Status.PRECONDITION_FAILED, MESSAGE_MISSING_DEMAND_ID );
}
// check id demand_type_id is numeric
if ( !StringUtils.isNumeric( notif.getDemand( ).getTypeId( ) ) )
{
return generateErrorMessage( notif, Response.Status.PRECONDITION_FAILED, MESSAGE_INCORRECT_DEMAND_ID );
}
// check if demand type id exists
if ( !DemandTypeHome.findByPrimaryKey( Integer.parseInt( notif.getDemand( ).getTypeId( ) ) ).isPresent( ) )
{
return generateErrorMessage( notif, Response.Status.PRECONDITION_FAILED, MESSAGE_INCORRECT_DEMAND_ID );
}
return StringUtils.EMPTY;
}
/**
* Generates the error message
* @param notification
* @param strResponseStatus
* @param strErrorMessage
* @return
*/
private String generateErrorMessage ( Notification notification, Status strResponseStatus, String strErrorMessage)
{
StringBuilder message = new StringBuilder();
message.append( "\n" );
message.append( "Demande id " + notification.getDemand( ).getDemandId( ) + "\n" );
message.append( "Notification id " + notification.getId( ) + "\n" );
message.append( "Status: " + strResponseStatus.getStatusCode( ) + " " + strResponseStatus.getReasonPhrase( ) + "\n" );
message.append( "Error: " + strErrorMessage + "\n" );
return message.toString( );
}
/**
* Web Service methode which permit to store the notification flow into a data store
*
* @param strJson
* The JSON flow
* @return The response
*/
@POST
@Path( "notificationEvent" )
@Consumes( MediaType.APPLICATION_JSON )
@Produces( MediaType.APPLICATION_JSON )
public Response notificationEvent( String strJson )
{
try
{
// Format from JSON
ObjectMapper mapper = new ObjectMapper( );
mapper.configure( DeserializationFeature.UNWRAP_ROOT_VALUE, true );
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
NotificationEvent notificationEvent = mapper.readValue( strJson, NotificationEvent.class );
AppLogService.debug( "grusupply / notificationEvent - Received strJson : " + strJson );
store( notificationEvent );
}
catch( JsonParseException ex )
{
return fail( ex, Response.Status.BAD_REQUEST );
}
catch( JsonMappingException | NullPointerException ex )
{
return fail( ex, Response.Status.BAD_REQUEST );
}
catch( IOException ex )
{
return fail( ex, Response.Status.INTERNAL_SERVER_ERROR );
}
catch( Exception ex )
{
return fail( ex, Response.Status.INTERNAL_SERVER_ERROR );
}
return success( );
}
/**
* Stores a notification and the associated demand
*
* @param notification
* the notification to store
*/
private void store( Notification notification )
{
Demand demand = _demandService.findByPrimaryKey( notification.getDemand( ).getId( ), notification.getDemand( ).getTypeId( ) );
if ( demand == null || !demand.getCustomer( ).getId( ).equals( notification.getDemand( ).getCustomer( ).getConnectionId( ) ) )
{
demand = new Demand( );
demand.setId( notification.getDemand( ).getId( ) );
demand.setTypeId( notification.getDemand( ).getTypeId( ) );
demand.setSubtypeId( notification.getDemand( ).getSubtypeId( ) );
demand.setReference( notification.getDemand( ).getReference( ) );
demand.setCreationDate( notification.getDate( ) );
demand.setMaxSteps( notification.getDemand( ).getMaxSteps( ) );
demand.setCurrentStep( notification.getDemand( ).getCurrentStep( ) );
demand.setStatusId( getCalculedStatusId( notification ) );
Customer customerDemand = new Customer( );
customerDemand.setId( notification.getDemand( ).getCustomer( ).getId( ) );
customerDemand.setConnectionId( notification.getDemand( ).getCustomer( ).getConnectionId( ) );
demand.setCustomer( customerDemand );
_demandService.create( demand );
}
else
{
demand.getCustomer( ).setId( notification.getDemand( ).getCustomer( ).getId( ) );
demand.setCurrentStep( notification.getDemand( ).getCurrentStep( ) );
int nCalculedStatusId = getCalculedStatusId( notification );
// Demand opened to closed
if ( !EnumGenericStatus.isClosedStatus( demand.getStatusId( ) )
&& EnumGenericStatus.isClosedStatus( nCalculedStatusId ) )
{
demand.setClosureDate( notification.getDate( ) );
}
// Demand closed to opened
if ( EnumGenericStatus.isClosedStatus( demand.getStatusId( ) ) && !EnumGenericStatus.isClosedStatus( nCalculedStatusId ) )
{
demand.setClosureDate( 0 );
}
_demandService.update( demand );
}
notification.setDemand( demand );
_demandService.create( notification );
}
/**
* Calculates the generic status id for new notifications.
* @param notification
* @return
*/
private int getCalculedStatusId ( Notification notification )
{
if ( notification.getMyDashboardNotification( ) != null )
{
if( notification.getDemand( ) != null
&& EnumGenericStatus.existStatus( notification.getDemand( ).getStatusId( ) ) )
{
return notification.getDemand( ).getStatusId( );
}
else if ( EnumGenericStatus.existStatus( notification.getMyDashboardNotification( ).getStatusId( ) ) )
{
return notification.getMyDashboardNotification( ).getStatusId( );
}
else
{
Optional<fr.paris.lutece.plugins.grustoragedb.business.Status> status = StatusHome.findByStatus( notification.getMyDashboardNotification( ).getStatusText( ) );
if ( status.isPresent( ) )
{
EnumGenericStatus genericStatus = EnumGenericStatus.valueOf( status.get( ).getCodeStatus( ) );
if ( genericStatus != null )
{
return genericStatus.getStatusId( );
}
}
}
return -1;
}
return notification.getDemand( ).getStatusId( );
}
/**
* Stores a notification and the associated demand
*
* @param notification
* the notification to store
*/
private void store( NotificationEvent notificationEvent )
{
_demandService.create( notificationEvent );
}
/**
* Build an error response
*
* @param strMessage
* The error message
* @param ex
* An exception
* @return The response
*/
private Response fail( Throwable ex, Status httpStatus )
{
StringBuilder strMsg = new StringBuilder( "[" );
if ( ex != null )
{
AppLogService.error( ex.getMessage( ), ex );
strMsg.append( new StatusMessage( TYPE_NOTIFICATION, STATUS_ERROR, ex.toString( ) , ex.getMessage( ) ).asJson( ) );
}
strMsg.append( "]" );
String strError = "{ \"acknowledge\" : { \"status\": \"error\", \"errors\" : " + strMsg + " } }";
return Response.status( httpStatus ).entity( strError ).build( );
}
/**
* Build an error response
*
* @param strMessage
* The error message
* @param ex
* An exception
* @return The response
*/
private Response successWithWarnings( List<StatusMessage> warnings )
{
StringBuilder strWarnings = new StringBuilder( "[" );
if (warnings != null)
{
for ( StatusMessage msg : warnings )
{
strWarnings.append( msg.asJson( ) ).append( "," );
}
// remove last ","
strWarnings.setLength( strWarnings.length( ) - 1);
}
strWarnings.append( "]" );
String strResponse = "{ \"acknowledge\" : { \"status\": \"warning\", \"warnings\" : " + strWarnings.toString( ) + " } }";
return Response.status( Response.Status.CREATED ).entity( strResponse ).build( );
}
/**
* success case
*
* @return a successful response
*/
private Response success( )
{
return Response.status( Response.Status.CREATED ).entity( RESPONSE_OK ).build( );
}
}