Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.ctoolkit.restapi.client.adapter.RestFacadeAdapter Maven / Gradle / Ivy
/*
* Copyright (c) 2017 Comvai, s.r.o. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ctoolkit.restapi.client.adapter;
import com.google.api.client.googleapis.media.MediaHttpDownloader;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.common.collect.Lists;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.metadata.Type;
import ma.glasnost.orika.metadata.TypeFactory;
import org.ctoolkit.restapi.client.ClientErrorException;
import org.ctoolkit.restapi.client.DeleteIdentification;
import org.ctoolkit.restapi.client.DownloadMediaProvider;
import org.ctoolkit.restapi.client.DownloadRequest;
import org.ctoolkit.restapi.client.HttpFailureException;
import org.ctoolkit.restapi.client.Identifier;
import org.ctoolkit.restapi.client.NotFoundException;
import org.ctoolkit.restapi.client.PayloadRequest;
import org.ctoolkit.restapi.client.RemoteServerErrorException;
import org.ctoolkit.restapi.client.RequestCredential;
import org.ctoolkit.restapi.client.RequestTimeoutException;
import org.ctoolkit.restapi.client.RestFacade;
import org.ctoolkit.restapi.client.RetrievalRequest;
import org.ctoolkit.restapi.client.SimpleRequest;
import org.ctoolkit.restapi.client.SingleRetrievalIdentification;
import org.ctoolkit.restapi.client.UnauthorizedException;
import org.ctoolkit.restapi.client.UpdateIdentification;
import org.ctoolkit.restapi.client.UploadMediaProvider;
import org.ctoolkit.restapi.client.adaptee.DeleteExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.DownloadExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.GetExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.InsertExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.ListExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.MediaProvider;
import org.ctoolkit.restapi.client.adaptee.NewExecutorAdaptee;
import org.ctoolkit.restapi.client.adaptee.UnderlyingClientAdaptee;
import org.ctoolkit.restapi.client.adaptee.UpdateExecutorAdaptee;
import org.ctoolkit.restapi.client.googleapis.GoogleApiProxyFactory;
import org.ctoolkit.restapi.client.provider.LocalListResourceProvider;
import org.ctoolkit.restapi.client.provider.LocalResourceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The resource facade implementation as adapter. Executes Java bean mapping
* and then delegates the execution to one of the binded adaptee.
*
* @author Aurel Medvegy
* @see MapperFactory
*/
public class RestFacadeAdapter
implements RestFacade
{
private static final Logger logger = LoggerFactory.getLogger( RestFacadeAdapter.class );
private final MapperFacade mapper;
private final MapperFactory factory;
private final ResourceProviderInjector injector;
private final GoogleApiProxyFactory apiFactory;
@Inject
RestFacadeAdapter( MapperFacade mapper,
MapperFactory factory,
ResourceProviderInjector injector,
GoogleApiProxyFactory apiFactory )
{
this.mapper = mapper;
this.factory = factory;
this.injector = injector;
this.apiFactory = apiFactory;
}
/**
* Returns the mapper instance.
*
* @return the mapper instance
*/
MapperFacade getMapper()
{
return mapper;
}
/**
* Executes download. Call to remote endpoint.
*
* @param downloader the http downloader to interact with
* @param adaptee the download adaptee configured for given resource
* @param resource the type of resource to download as a media
* @param identifier the unique identifier of content to download
* @param output the output stream where desired content will be downloaded to.
* @param interceptor the response interceptor
* @param headers the HTTP request headers
* @param params the optional resource params
* @param locale the language the client has configured to prefer in results if applicable
* @return Void
*/
Map executeDownload( @Nonnull MediaHttpDownloader downloader,
@Nonnull DownloadExecutorAdaptee adaptee,
@Nonnull Class resource,
@Nonnull Identifier identifier,
@Nonnull OutputStream output,
@Nonnull DownloadResponseInterceptor interceptor,
@Nullable HttpHeaders headers,
@Nullable Map params,
@Nullable Locale locale )
{
checkNotNull( downloader );
checkNotNull( adaptee );
checkNotNull( resource );
checkNotNull( identifier );
checkNotNull( output );
checkNotNull( interceptor );
//noinspection MismatchedQueryAndUpdateOfCollection
RequestCredential credential = new RequestCredential();
credential.fillInFrom( params, false );
String type = headers == null ? null : headers.getContentType();
URL path = adaptee.prepareDownloadUrl( identifier.root(), type, params, locale );
if ( path == null )
{
String msg = "URL to download a resource content cannot be null. Identifier: ";
throw new IllegalArgumentException( msg + identifier + " Resource: " + resource.getName() );
}
try
{
downloader.download( new GenericUrl( path ), headers, output );
}
catch ( IOException e )
{
// Update exception handling is being used. We need to return exception if resource is not found.
throw prepareUpdateException( e, resource, identifier );
}
return interceptor.getHeaders();
}
/**
* Prepares the configured download request
*
* @param resource the type of resource to download as a media
* @param identifier the unique identifier of content to download
* @param output the output stream where desired content will be downloaded to.
* @param type the content type or {@code null} to expect default
* @return the configured download request
*/
DownloadRequest prepareDownloadRequest( @Nonnull Class resource,
@Nonnull Identifier identifier,
@Nonnull OutputStream output,
@Nullable String type )
{
checkNotNull( resource );
checkNotNull( identifier );
checkNotNull( output );
DownloadResponseInterceptor interceptor = new DownloadResponseInterceptor();
DownloadExecutorAdaptee adaptee = adaptee( DownloadExecutorAdaptee.class, resource );
String apiPrefix = adaptee.getApiPrefix();
MediaHttpDownloader downloader;
try
{
HttpRequestInitializer requestConfig = apiFactory.newRequestConfig( apiPrefix, interceptor );
downloader = new MediaHttpDownloader( apiFactory.getHttpTransport(), requestConfig );
}
catch ( GeneralSecurityException e )
{
logger.error( "Application name: " + apiFactory.getApplicationName( apiPrefix )
+ " Endpoint URL: " + apiFactory.getEndpointUrl( apiPrefix ), e );
throw new UnauthorizedException( e.getMessage() );
}
catch ( IOException e )
{
logger.error( "Application name: " + apiFactory.getApplicationName( apiPrefix )
+ " Endpoint URL: " + apiFactory.getEndpointUrl( apiPrefix ), e );
throw new RemoteServerErrorException( HttpStatusCodes.STATUS_CODE_SERVER_ERROR, e.getMessage() );
}
Identifier root = identifier.root();
return new DownloadRequestImpl( this, adaptee, downloader, resource, root, output, interceptor, type );
}
@Override
public PayloadRequest newInstance( @Nonnull Class resource )
{
checkNotNull( resource );
NewExecutorAdaptee adaptee = adaptee( NewExecutorAdaptee.class, resource );
Object remoteRequest;
try
{
remoteRequest = adaptee.prepareNew( resource.getSimpleName() );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
return new NewInstanceRequest<>( resource, this, adaptee, remoteRequest );
}
@Override
public UploadMediaProvider upload( @Nonnull T resource )
{
checkNotNull( resource );
return new InputStreamUploadMediaRequestProvider<>( this, resource );
}
@Override
public DownloadMediaProvider download( @Nonnull Class resource )
{
checkNotNull( resource );
return new OutputStreamDownloadMediaRequestProvider( this, resource );
}
R callbackNewInstance( @Nonnull NewExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class responseType,
@Nullable Map parameters,
@Nullable Locale locale )
{
checkNotNull( adaptee );
checkNotNull( remoteRequest );
checkNotNull( responseType );
if ( parameters == null )
{
parameters = new HashMap<>();
}
Object remoteInstance;
try
{
remoteInstance = adaptee.executeNew( remoteRequest, parameters, locale );
}
catch ( IOException e )
{
throw prepareUpdateException( e, responseType, null );
}
if ( remoteInstance.getClass() == responseType )
{
//noinspection unchecked
return ( R ) remoteInstance;
}
else
{
return mapper.map( remoteInstance, responseType );
}
}
@Override
public SingleRetrievalIdentification get( @Nonnull Class resource )
{
return new SingleRetrievalIdentificationImpl<>( this, resource );
}
RetrievalRequest internalGet( @Nonnull Class resource, @Nonnull Identifier identifier )
{
checkNotNull( resource );
checkNotNull( identifier, Identifier.class.getSimpleName() + " for GET operation cannot be null." );
GetExecutorAdaptee adaptee = adaptee( GetExecutorAdaptee.class, resource );
Object remoteRequest;
try
{
//noinspection unchecked
remoteRequest = adaptee.prepareGet( identifier );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
return new GetRequest<>( resource, identifier.root(), this, adaptee, remoteRequest );
}
R callbackExecuteGet( @Nonnull GetExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class responseType,
@Nonnull Identifier identifier,
@Nullable Map parameters,
@Nullable Locale locale )
{
checkNotNull( responseType );
if ( parameters == null )
{
parameters = new HashMap<>();
}
// looking for LocalResourceProvider optional implementation for given resource type
LocalResourceProvider provider = injector.getExistingResourceProvider( responseType );
R response = null;
boolean requestForPersist = false;
if ( provider != null )
{
// retrieve requested local resource
response = provider.get( identifier.root(), parameters, locale );
requestForPersist = response == null;
}
if ( response == null )
{
Object remoteObject;
try
{
remoteObject = adaptee.executeGet( remoteRequest, parameters, locale );
}
catch ( IOException e )
{
RuntimeException exception = prepareRetrievalException( e, responseType, identifier );
if ( exception == null )
{
return null;
}
else
{
throw exception;
}
}
if ( remoteObject.getClass() == responseType )
{
//noinspection unchecked
response = ( R ) remoteObject;
}
else
{
response = mapper.map( remoteObject, responseType );
}
}
if ( requestForPersist && response != null )
{
// TODO resource provider lastFor not implemented yet
// provide remote resource instance to be either persisted or cached
provider.persist( response, identifier.root(), parameters, locale, null );
}
return response;
}
@Override
public ListRequest list( @Nonnull Class resource )
{
return list( resource, null );
}
@Override
public ListRequest list( @Nonnull Class resource, @Nullable Identifier parent )
{
checkNotNull( resource );
ListExecutorAdaptee adaptee = adaptee( ListExecutorAdaptee.class, resource );
Object remoteRequest;
try
{
//noinspection unchecked
remoteRequest = adaptee.prepareList( parent == null ? null : parent.root() );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
return new ListRequest<>( resource, this, adaptee, remoteRequest );
}
List callbackExecuteList( @Nonnull ListExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class responseType,
@Nullable Map criteria,
@Nullable Locale locale,
int start,
int length,
@Nullable String orderBy,
@Nullable Boolean ascending )
{
checkNotNull( responseType );
if ( criteria == null )
{
criteria = new HashMap<>();
}
// looking for LocalResourceProvider optional implementation for given resource type
LocalListResourceProvider provider = injector.getExistingListResourceProvider( responseType );
List response = null;
boolean requestForPersist = false;
if ( provider != null )
{
// retrieve requested local list of resources
response = provider.list( criteria, locale, null );
requestForPersist = response == null;
}
if ( response == null )
{
List> remoteList;
try
{
remoteList = adaptee.executeList( remoteRequest, criteria, locale, start, length, orderBy, ascending );
}
catch ( IOException e )
{
RuntimeException exception = prepareRetrievalException( e, responseType, null );
if ( exception == null )
{
remoteList = null;
}
else
{
throw exception;
}
}
if ( remoteList == null || remoteList.isEmpty() )
{
response = Lists.newArrayList();
}
else
{
if ( remoteList.get( 0 ).getClass() == responseType )
{
//noinspection unchecked
response = ( List ) remoteList;
}
else
{
response = mapper.mapAsList( remoteList, responseType );
}
}
}
if ( requestForPersist && response != null && !response.isEmpty() )
{
// provide remote list of resource to be either persisted or cached
provider.persistList( response, criteria, locale, null );
}
return response;
}
@Override
public PayloadRequest insert( @Nonnull T resource )
{
return insert( resource, null );
}
@Override
public PayloadRequest insert( @Nonnull T resource, @Nullable Identifier parentKey )
{
return internalInsert( resource, parentKey, null );
}
PayloadRequest internalInsert( @Nonnull T resource,
@Nullable Identifier parentKey,
@Nullable MediaProvider provider )
{
checkNotNull( resource );
Class> remoteResource = evaluateRemoteResource( resource.getClass() );
Object source;
if ( resource.getClass() == remoteResource )
{
source = resource;
}
else
{
source = mapper.map( resource, remoteResource );
}
@SuppressWarnings( "unchecked" )
InsertExecutorAdaptee adaptee = adaptee( InsertExecutorAdaptee.class, resource.getClass() );
Object remoteRequest;
try
{
remoteRequest = adaptee.prepareInsert( source, parentKey == null ? null : parentKey.root(), provider );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
@SuppressWarnings( "unchecked" )
Class resourceClass = ( Class ) resource.getClass();
return new InsertRequest<>( resourceClass, parentKey, this, adaptee, remoteRequest );
}
R callbackExecuteInsert( @Nonnull InsertExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class responseType,
@Nullable Identifier parentKey,
@Nullable Map parameters,
@Nullable Locale locale )
{
checkNotNull( adaptee );
checkNotNull( remoteRequest );
checkNotNull( responseType );
Object source;
try
{
source = adaptee.executeInsert( remoteRequest, parameters, locale );
}
catch ( IOException e )
{
throw prepareUpdateException( e, responseType, parentKey );
}
if ( source == null )
{
return null;
}
if ( source.getClass() == responseType )
{
//noinspection unchecked
return ( R ) source;
}
else
{
return mapper.map( source, responseType );
}
}
@Override
public UpdateIdentification update( @Nonnull T resource )
{
return new UpdateIdentificationImpl<>( this, resource );
}
PayloadRequest internalUpdate( @Nonnull T resource,
@Nonnull Identifier identifier,
@Nullable MediaProvider provider )
{
checkNotNull( resource );
checkNotNull( identifier, Identifier.class.getSimpleName() + " for UPDATE operation cannot be null." );
Class> remoteResource = evaluateRemoteResource( resource.getClass() );
Object source;
if ( resource.getClass() == remoteResource )
{
source = resource;
}
else
{
source = mapper.map( resource, remoteResource );
}
@SuppressWarnings( "unchecked" )
UpdateExecutorAdaptee adaptee = adaptee( UpdateExecutorAdaptee.class, resource.getClass() );
Object remoteRequest;
try
{
remoteRequest = adaptee.prepareUpdate( source, identifier.root(), provider );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
@SuppressWarnings( "unchecked" )
Class resourceClass = ( Class ) resource.getClass();
return new UpdateRequest<>( resourceClass, identifier, this, adaptee, remoteRequest );
}
R callbackExecuteUpdate( @Nonnull UpdateExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class responseType,
@Nonnull Object identifier,
@Nullable Map parameters,
@Nullable Locale locale )
{
checkNotNull( adaptee );
checkNotNull( remoteRequest );
checkNotNull( responseType );
checkNotNull( identifier );
Object source;
try
{
source = adaptee.executeUpdate( remoteRequest, parameters, locale );
}
catch ( IOException e )
{
throw prepareUpdateException( e, responseType, identifier );
}
if ( source == null )
{
return null;
}
if ( source.getClass() == responseType )
{
//noinspection unchecked
return ( R ) source;
}
else
{
return mapper.map( source, responseType );
}
}
@Override
public DeleteIdentification delete( @Nonnull Class resource )
{
return new DeleteIdentificationImpl<>( this, resource );
}
@Override
public C client( @Nonnull Class type )
{
@SuppressWarnings( "unchecked" )
UnderlyingClientAdaptee adaptee = adaptee( UnderlyingClientAdaptee.class, type );
return adaptee.getUnderlyingClient();
}
@SuppressWarnings( "unchecked" )
SimpleRequest internalDelete( @Nonnull Class resource, @Nonnull Identifier identifier )
{
checkNotNull( resource );
checkNotNull( identifier );
DeleteExecutorAdaptee adaptee = adaptee( DeleteExecutorAdaptee.class, resource );
Object remoteRequest;
try
{
remoteRequest = adaptee.prepareDelete( identifier.root() );
}
catch ( IOException e )
{
throw new ClientErrorException( 400, e.getMessage() );
}
return ( SimpleRequest ) new DeleteRequest( resource, identifier, this, adaptee, remoteRequest );
}
Void callbackExecuteDelete( @Nonnull DeleteExecutorAdaptee adaptee,
@Nonnull Object remoteRequest,
@Nonnull Class resource,
@Nonnull Object identifier,
@Nullable Map parameters,
@Nullable Locale locale )
{
checkNotNull( identifier );
try
{
adaptee.executeDelete( remoteRequest, parameters, locale );
}
catch ( IOException e )
{
throw prepareUpdateException( e, resource, identifier );
}
return null;
}
private RuntimeException prepareRetrievalException( IOException e, Class> resource, @Nullable Object identifier )
{
return prepareException( e, resource, identifier, false );
}
private RuntimeException prepareUpdateException( IOException e, Class> resource, @Nullable Object identifier )
{
return prepareException( e, resource, identifier, true );
}
private RuntimeException prepareException( IOException e,
Class> resource,
@Nullable Object identifier,
boolean update )
{
int statusCode = -1;
String statusMessage;
RuntimeException toBeThrown;
if ( e instanceof HttpResponseException )
{
statusCode = ( ( HttpResponseException ) e ).getStatusCode();
statusMessage = ( ( HttpResponseException ) e ).getStatusMessage();
}
else if ( e instanceof SocketTimeoutException )
{
statusCode = 408;
statusMessage = e.getMessage();
}
else if ( e instanceof UnknownHostException )
{
statusCode = HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE;
statusMessage = "Unknown host: " + e.getMessage();
}
else if ( e instanceof SSLHandshakeException )
{
statusCode = HttpStatusCodes.STATUS_CODE_UNAUTHORIZED;
statusMessage = e.getMessage();
}
else
{
statusMessage = e.getMessage();
}
logger.warn( "Resource " + resource.getName() + ", identifier: " + identifier, e );
if ( 400 == statusCode )
{
toBeThrown = new ClientErrorException( statusCode, statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_UNAUTHORIZED == statusCode )
{
toBeThrown = new UnauthorizedException( statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_FORBIDDEN == statusCode )
{
toBeThrown = new ClientErrorException( statusCode, statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_NOT_FOUND == statusCode && update )
{
toBeThrown = new NotFoundException( statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_NOT_FOUND == statusCode )
{
toBeThrown = new NotFoundException( statusMessage );
}
else if ( 408 == statusCode )
{
toBeThrown = new RequestTimeoutException( statusMessage );
}
else if ( 409 == statusCode )
{
toBeThrown = new ClientErrorException( statusCode, statusMessage );
}
else if ( 400 < statusCode && statusCode < 499 )
{
toBeThrown = new ClientErrorException( statusCode, statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_SERVER_ERROR == statusCode )
{
toBeThrown = new RemoteServerErrorException( statusCode, statusMessage );
}
else if ( HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE == statusCode )
{
toBeThrown = new RemoteServerErrorException( statusCode, statusMessage );
}
else if ( statusCode > -1 )
{
toBeThrown = new HttpFailureException( statusCode, statusMessage );
}
else
{
toBeThrown = new RuntimeException( statusMessage );
}
return toBeThrown;
}
Class> evaluateRemoteResource( Class resource )
{
Set> types = factory.lookupMappedClasses( TypeFactory.valueOf( resource ) );
Iterator> iterator = types.iterator();
Class> remoteResource;
if ( iterator.hasNext() )
{
remoteResource = iterator.next().getRawType();
}
else
{
// there is no mapping, use directly the given resource type
remoteResource = resource;
}
return remoteResource;
}
private A adaptee( Class adapteeType, Class> resource )
{
Class> remoteResource = evaluateRemoteResource( resource );
A adaptee = injector.getExecutorAdaptee( adapteeType, remoteResource );
if ( adaptee == null && remoteResource == resource )
{
String msg = "Missing binding between adaptee and resource: " + adapteeType.getSimpleName() + "<"
+ resource.getName() + ">";
throw new NotFoundException( msg );
}
if ( adaptee == null )
{
String msg = "Missing binding between adaptee and remote resource: " + adapteeType.getSimpleName() + "<"
+ remoteResource.getName() + ">. The remote resource " + remoteResource.getName()
+ " is being mapped to " + resource.getName() + ".";
throw new NotFoundException( msg );
}
return adaptee;
}
}