package com.paymill.services;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import com.paymill.utils.HttpClient;
import com.paymill.utils.ParameterMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.paymill.context.PaymillContext;
import com.paymill.exceptions.PaymillException;
import com.paymill.models.PaymillList;
import com.paymill.models.SnakeCase;
import com.paymill.models.Updateable;
final class RestfulUtils {
private final static String ENDPOINT = "https://api.paymill.com/v2.1";
static <T> PaymillList<T> list( String path, Object filter, Object order, Integer count, Integer offset, Class<?> clazz, HttpClient httpClient ) {
ParameterMap<String, String> params = RestfulUtils.prepareFilterParameters( filter );
String param = RestfulUtils.prepareOrderParameter( order );
if( StringUtils.isNotBlank( param ) && !StringUtils.startsWith( param, "_" ) ) {
params.add( "order", param );
}
if( count != null && count > 0 ) {
params.add( "count", String.valueOf( count ) );
}
if( offset != null && offset >= 0 ) {
params.add( "offset", String.valueOf( offset ) );
}
return RestfulUtils.deserializeList( httpClient.get( ENDPOINT + path, params ), clazz );
}
static <T> T show( String path, T target, Class<?> clazz, HttpClient httpClient ) {
String id = RestfulUtils.getIdByReflection( target );
T source = RestfulUtils.deserializeObject( httpClient.get( ENDPOINT + path + "/" + id ), clazz );
return RestfulUtils.refreshInstance( source, target );
}
static <T> T create( String path, ParameterMap<String, String> params, Class<T> clazz, HttpClient httpClient ) {
return RestfulUtils.deserializeObject( httpClient.post( ENDPOINT + path, params ), clazz );
}
static <T> T update( String path, T target, Class<?> clazz, HttpClient httpClient ) {
ParameterMap<String, String> params = RestfulUtils.prepareEditableParameters( target );
String id = RestfulUtils.getIdByReflection( target );
T source = RestfulUtils.deserializeObject( httpClient.put( ENDPOINT + path + "/" + id, params ), clazz );
return RestfulUtils.refreshInstance( source, target );
}
static <T> T update( String path, T target, ParameterMap<String, String> params, boolean includeTargetUpdateables, Class<?> clazz, HttpClient httpClient ) {
String id = RestfulUtils.getIdByReflection( target );
if( includeTargetUpdateables ) {
params.putAll( RestfulUtils.prepareEditableParameters( target ) );
}
T source = RestfulUtils.deserializeObject( httpClient.put( ENDPOINT + path + "/" + id, params ), clazz );
return RestfulUtils.refreshInstance( source, target );
}
static <T> T delete( String path, T target, ParameterMap<String, String> params, Class<?> clazz, HttpClient httpClient ) {
String id = RestfulUtils.getIdByReflection( target );
T source = RestfulUtils.deserializeObject( httpClient.delete( ENDPOINT + path + "/" + id, params ), clazz );
return RestfulUtils.refreshInstance( source, target );
}
static <T> T delete( String path, T target, Class<?> clazz, HttpClient httpClient ) {
String id = RestfulUtils.getIdByReflection( target );
T source = RestfulUtils.deserializeObject( httpClient.delete( ENDPOINT + path + "/" + id, null ), clazz );
return RestfulUtils.refreshInstance( source, target );
}
private static String getIdByReflection( Object instance ) {
if( instance == null )
throw new RuntimeException( "Can not obtain Id from null" );
try {
Field field = instance.getClass().getDeclaredField( "id" );
field.setAccessible( true );
String id = String.valueOf( field.get( instance ) );
ValidationUtils.validatesId( id );
return id;
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
}
@SuppressWarnings( "unchecked" )
private static <T> T deserializeObject( String content, Class<?> clazz ) {
try {
JsonNode wrappedNode = PaymillContext.PARSER.readValue( content, JsonNode.class );
if( wrappedNode.has( "data" ) ) {
JsonNode dataNode = wrappedNode.get( "data" );
if( !dataNode.isArray() ) {
return (T) PaymillContext.PARSER.readValue( dataNode.toString(), clazz );
}
}
if( wrappedNode.has( "error" ) ) {
throw new PaymillException( wrappedNode.get( "error" ).toString() );
}
} catch( IOException exc ) {
throw new RuntimeException( exc );
}
return null;
}
@SuppressWarnings( "unchecked" )
private static <T> PaymillList<T> deserializeList( String content, Class<?> clazz ) {
try {
JsonNode wrappedNode = PaymillContext.PARSER.readValue( content, JsonNode.class );
PaymillList<T> wrapper = PaymillContext.PARSER.readValue( wrappedNode.toString(), PaymillList.class );
if( wrappedNode.has( "data" ) ) {
JsonNode dataNode = wrappedNode.get( "data" );
if( dataNode.isArray() ) {
List<T> objects = new ArrayList<T>();
for( Object object : PaymillContext.PARSER.readValue( wrappedNode.toString(), PaymillList.class ).getData() ) {
try {
objects.add( (T) PaymillContext.PARSER.readValue( PaymillContext.PARSER.writeValueAsString( object ), clazz ) );
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
}
wrapper.setData( objects );
return wrapper;
}
}
if( wrappedNode.has( "error" ) ) {
throw new PaymillException( wrappedNode.get( "error" ).toString() );
}
} catch( IOException exc ) {
throw new RuntimeException( exc );
}
return null;
}
private static ParameterMap<String, String> prepareEditableParameters( Object instance ) {
ParameterMap<String, String> params = new ParameterMap<String, String>();
for( Field field : instance.getClass().getDeclaredFields() ) {
Updateable updateable = field.getAnnotation( Updateable.class );
if( updateable != null ) {
try {
field.setAccessible( true );
Object value = field.get( instance );
if( value != null ) {
Class<?> clazz = value.getClass();
if( ClassUtils.isPrimitiveOrWrapper( clazz ) || ClassUtils.getSimpleName( clazz ).equals( "String" ) ) {
params.add( updateable.value(), String.valueOf( value ) );
} else {
// not primitive type, assume ID
String id = null;
try {
id = RestfulUtils.getIdByReflection( value );
} catch( Exception e ) {
//that's normal, object does not have an id
}
if( id != null ) {
params.add( updateable.value(), id );
} else {
params.add( updateable.value(), value.toString() );
}
}
}
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
}
}
return params;
}
private static ParameterMap<String, String> prepareFilterParameters( Object instance ) {
ParameterMap<String, String> params = new ParameterMap<String, String>();
if( instance == null )
return params;
try {
for( Field field : instance.getClass().getDeclaredFields() ) {
field.setAccessible( true );
Object value = field.get( instance );
if( value != null ) {
params.add( field.getAnnotation( SnakeCase.class ).value(), String.valueOf( value ) );
}
}
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
return params;
}
private static String prepareOrderParameter( Object instance ) {
if( instance == null )
return StringUtils.EMPTY;
String order = StringUtils.EMPTY;
String sortEntry = StringUtils.EMPTY;
try {
for( Field field : instance.getClass().getDeclaredFields() ) {
field.setAccessible( true );
if( field.getBoolean( instance ) ) {
SnakeCase annotation = field.getAnnotation( SnakeCase.class );
if( annotation.order() ) {
order += "_" + annotation.value();
} else {
sortEntry = annotation.value();
}
}
}
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
return sortEntry + order;
}
private static <T> T refreshInstance( T source, T target ) {
if( source == null ) {
return target;
}
try {
BeanUtils.copyProperties( target, source );
} catch( Exception exc ) {
throw new RuntimeException( exc );
}
return target;
}
}