gt:             return mapper.map( remoteInstance, responseType );
context: /*
   * 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 com.google.inject.Binding;
  import com.google.inject.Injector;
  import com.google.inject.Key;
  import com.google.inject.TypeLiteral;
  import com.google.inject.util.Types;
  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.ForbiddenException;
  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.RestFacade;
  import org.ctoolkit.restapi.client.RetrievalRequest;
  import org.ctoolkit.restapi.client.ServiceUnavailableException;
  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.provider.LocalListResourceProvider;
  import org.ctoolkit.restapi.client.provider.LocalResourceProvider;
  import org.ctoolkit.restapi.client.provider.TokenProvider;
  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.lang.reflect.ParameterizedType;
  import java.net.SocketTimeoutException;
  import java.net.URL;
  import java.net.UnknownHostException;
  import java.security.GeneralSecurityException;
  import java.util.Collection;
  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;
  import static java.util.Objects.requireNonNull;
  
  /**
   * The Fluent facade API implementation as an adapter. Executes Java bean mapping
   * and then delegates the execution to one of the binded adaptee.
   *
   * @author <a href="mailto:aurel.medvegy@ctoolkit.org">Aurel Medvegy</a>
   * @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 Injector injector;
  
      private final GoogleApiProxyFactory apiFactory;
  
      @SuppressWarnings( "rawtypes" )
      private final Map<String, ClientApi> apis;
  
      private Substitute substitute;
  
      @SuppressWarnings( "rawtypes" )
      @Inject
      RestFacadeAdapter( MapperFacade mapper,
                         MapperFactory factory,
                         Injector injector,
                         GoogleApiProxyFactory apiFactory,
                         Map<String, ClientApi> apis )
      {
          this.mapper = mapper;
          this.factory = factory;
          this.injector = injector;
          this.apiFactory = apiFactory;
          this.apis = apis;
      }
  
      @com.google.inject.Inject( optional = true )
      public void setSubstitute( Substitute substitute )
      {
          this.substitute = substitute;
      }
  
      /**
       * 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 grh         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 the download's response headers.
       */
      Map<String, Object> executeDownload( @Nonnull MediaHttpDownloader downloader,
                                           @Nonnull DownloadExecutorAdaptee<?> adaptee,
                                           @Nonnull Class<?> resource,
                                           @Nonnull Identifier identifier,
                                           @Nonnull OutputStream output,
                                           @Nonnull DownloadResponseInterceptor interceptor,
                                           @Nonnull GoogleRequestHeaders grh,
                                           @Nullable Map<String, Object> params,
                                           @Nullable Locale locale )
      {
          //noinspection MismatchedQueryAndUpdateOfCollection
          RequestCredential credential = new RequestCredential();
          credential.fillInFrom( params, false );
          HttpHeaders headers = grh.getHeaders();
          String type = headers == null ? null : headers.getContentType();
  
          URL path = requireNonNull( 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
          {
              boolean remote = substitute == null;
              if ( !remote )
              {
                  try
                  {
                      substitute.download( checkNotNull( resource ),
                              identifier,
                              checkNotNull( output ),
                              headers,
                              params,
                              locale );
                  }
                  catch ( Substitute.ProceedWithRemoteCall e )
                  {
                      remote = true;
                  }
              }
  
              if ( remote )
              {
                  grh.setAuthorizationIf( this::getTokenProvider );
                  requireNonNull( 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 )
      {
          DownloadResponseInterceptor interceptor = new DownloadResponseInterceptor();
          DownloadExecutorAdaptee<?> adaptee = adaptee( DownloadExecutorAdaptee.class, checkNotNull( 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( e.getMessage() );
          }
  
          Identifier root = checkNotNull( identifier ).root();
          OutputStream os = checkNotNull( output );
          return new DownloadRequestImpl( this, adaptee, downloader, resource, root, os, interceptor, type );
      }
  
      @Override
      public <T> PayloadRequest<T> newInstance( @Nonnull Class<T> resource )
      {
          NewExecutorAdaptee<?> adaptee = adaptee( NewExecutorAdaptee.class, checkNotNull( resource ) );
          Object remoteRequest;
          try
          {
              remoteRequest = adaptee.prepareNew( resource.getSimpleName() );
          }
          catch ( IOException e )
          {
              throw new ClientErrorException( e.getMessage() );
          }
  
          return new NewInstanceRequest<>( resource, this, adaptee, remoteRequest );
      }
  
      @Override
      public <T> UploadMediaProvider<T> upload( @Nonnull T resource )
      {
          return new InputStreamUploadMediaRequestProvider<>( this, checkNotNull( resource ) );
      }
  
      @Override
      public <T> DownloadMediaProvider download( @Nonnull Class<T> resource )
      {
          return new OutputStreamDownloadMediaRequestProvider( this, checkNotNull( resource ) );
      }
  
      <R> R callbackNewInstance( @Nonnull NewExecutorAdaptee<?> adaptee,
                                 @Nonnull Object remoteRequest,
                                 @Nonnull Class<R> responseType,
                                 @Nonnull GoogleRequestHeaders headers,
                                 @Nullable Map<String, Object> parameters,
                                 @Nullable Locale locale )
      {
          if ( parameters == null )
          {
              parameters = new HashMap<>();
          }
  
          Object remoteInstance = null;
          try
          {
              boolean remote = substitute == null;
              if ( !remote )
              {
                  try
                  {
                      remoteInstance = substitute.newInstance( checkNotNull( remoteRequest ),
                              checkNotNull( responseType ),
                              parameters,
                              locale );
                  }
                  catch ( Substitute.ProceedWithRemoteCall e )
                  {
                      remote = true;
                  }
              }
  
              if ( remote )
              {
                  headers.setAuthorizationIf( this::getTokenProvider );
                  remoteInstance = adaptee.executeNew( remoteRequest, parameters, locale );
              }
          }
          catch ( IOException e )
          {
              throw prepareUpdateException( e, responseType, null );
          }
  
          checkNotNull( remoteInstance, "Callback must not return null" );
          if ( remoteInstance.getClass() == responseType )
          {
              //noinspection unchecked
              return ( R ) remoteInstance;
          }
          else
          {
  