1

Using RestTemplate in Spring Boot to connect to a server, I have this generic class to map responses from the server I'm connecting to:

@Data
public class ServicesResponseDTO<T> implements Serializable {
    private String[] messages; 
    private String operation; 
    private String code;
    private List<T> data;
    
    private String start;
    private String limit;
    private String total;
    private String timeInMillis;
    private String message;
    
    
    public boolean esOK () {
        return "OK".equals(code);
    }
    
    public boolean esOK_NO_DATA () {
        return "OK_NO_DATA".equals(code);
    }
    
    public boolean noOK_USER_NO_LOGADO () {
        return "ERROR_NO_USER_LOGGED".equals(code);
    }
}

and this generic class to parse every call:

public class CustomRestClient<T extends BasicDTO> {

    private final Class<T> typeParameterClass;
    private ServiceClientFactory clientFactory;
    
    public CustomRestClient(Class<T> type, ServiceClientFactory clientFactory) {
        typeParameterClass = type;
        this.clientFactory = clientFactory;
    }
    
    private RestTemplate getRestTemplate() {
        return clientFactory.getRestTemplate();
    }
    
    public T doGet(UriComponentsBuilder builder) throws ServiceException {
        RestTemplate restTemplate = getRestTemplate();
        T responseEntity = null;
        String responseCode = null;
        try {
            ResponseEntity<String> response = restTemplate.getForEntity(builder.toUriString(), String.class);
            ObjectMapper mapper = JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();
            TypeFactory t = TypeFactory.defaultInstance();
            ServicesResponseDTO<T> respuesta = mapper.readValue(response.getBody(), t.constructParametricType(ServicesResponseDTO.class, typeParameterClass));
            responseCode = respuesta.getCode();
            if (respuesta.esOK_NO_DATA()) {
                return null;
            }
            if (!respuesta.esOK()) {
                log.warn(msgError);
                throw new ServiceException(msgError);
            }
            responseEntity = respuesta.getData()!=null && !respuesta.getData().isEmpty() ? respuesta.getData().get(0) : null;
            TripleDESCipher.decipher(responseEntity);
        } catch (JsonProcessingException e) {
            log.warn(msgError);
            throw new ServiceException(msgError);
        } catch (ResourceAccessException e) {
            if (e.getCause() instanceof HttpHostConnectException) {
                throw new ServiceException("Error connecting to the data origin");
            }
            if (e.getCause() instanceof SocketTimeoutException) {
                throw new ServiceException("Timeout awaiting data origin");
            }
            throw new ServiceException("No controlled error with data origin");
        } catch (Exception e) {
            throw new ServiceException("No controlled error with data origin");
        }
        return responseEntity;
    }
}

That is working.

But now RestTemplate is going to get deprecated and I'm migrating to RestClient, so did this:

public class CustomRestClient<T extends BasicDTO> {

    private final Class<T> typeParameterClass;
    
    private ServiceClientFactory clientFactory;
    
    public CustomRestClient(Class<T> type, ServiceClientFactory clientFactory) {
        typeParameterClass = type;
        this.clientFactory = clientFactory;
    }
    
    private RestClient getRestClient() {
        return clientFactory.getRestClient();
    }
    
    private ParameterizedTypeReference<ServicesResponseDTO<T>> getParameterizedTypeReference(Class<T> clazz) {
        return new ParameterizedTypeReference<ServicesResponseDTO<T>>() {};
    }
    
    public T doGet(UriComponentsBuilder builder) throws ServiceException {
        RestClient restClient = getRestClient();
        T responseEntity = null;
        String responseCode = null;
        try {
            ParameterizedTypeReference<ServicesResponseDTO<T>> tipo = getParameterizedTypeReference(typeParameterClass);
            ResponseEntity<ServicesResponseDTO<T>> response = restClient.get()
                    .uri(builder.toUriString()).accept(MediaType.APPLICATION_JSON).retrieve().toEntity(tipo);
            ServicesResponseDTO<T> respuesta = (ServicesResponseDTO<T>) response.getBody();
            responseCode = respuesta.getCode();
            if (respuesta.esOK_NO_DATA()) {
                return null;
            }
            if (!respuesta.esOK()) {
                log.warn(msgError);
                throw new ServiceException(msgError);
            }
            responseEntity = respuesta.getData()!=null && !respuesta.getDaa().isEmpty() ? respuesta.getData().get(0) : null;
            TripleDESCipher.decipher(responseEntity);
        } catch (ResourceAccessException e) {
            if (e.getCause() instanceof HttpHostConnectException) {
                throw new ServiceException("Error connecting to the data origin");
            }
            if (e.getCause() instanceof SocketTimeoutException) {
                log.warn("Timeout esperando la respuesta del endpoint. "+e.getLocalizedMessage());
                throw new ServiceException("Timeout awaiting data origin");
            }
            throw new ServiceException("No controlled error with data origin");
        } catch (Exception e) {
            throw new ServiceException("No controlled error with data origin");
        }
        return responseEntity;
    }
}

Basically, changed from RestTemplate to RestClient and the way to parse it.

The problem I'm facing is that ResponseEntity is getting parsed into ResponseEntity<ServicesResponseDTO<BasicDTO>> instead of ResponseEntity<ServicesResponseDTO<ClubDTO>>, while ClubDTO is:

@Data
@EqualsAndHashCode(callSuper = true)
public class FenixClubDTO extends BasicDTO {
data
}

But if I change the doGet method to return a ClubDTO and:

ParameterizedTypeReference<ServicesResponseDTO<T>> tipo = getParameterizedTypeReference(typeParameterClass);
ResponseEntity<ServicesResponseDTO<T>> response = restClient.get().uri(builder.toUriString()).accept(MediaType.APPLICATION_JSON).retrieve().toEntity(tipo);
ServicesResponseDTO<T> respuesta = (ServicesResponseDTO<T>) response.getBody();

to:

ParameterizedTypeReference<ServicesResponseDTO<ClubDTO>> tipo = new ParameterizedTypeReference<ServicesResponseDTO<ClubDTO>>() {};
ResponseEntity<ServicesResponseDTO<ClubDTO>> response = restClient.get()
                    .uri(builder.toUriString()).accept(MediaType.APPLICATION_JSON).retrieve().toEntity(tipo);
ServicesResponseDTO<FenixClubDTO> respuesta = (ServicesResponseDTO<ClubDTO>) response.getBody();

then it gets a ServiceResponseDTO<ClubDTO> (expected) instead of ServiceResponseDTO<BasicDTO>.

So I guess the problem is with the ParameterizedTypeReference that's using a type T that's also extending.

With this workaround it works, but I really dislike it. It consists in creating every return type in the config and not only the client.

@Configuration
public class ApiClientConfiguration {
    @Bean
    public ParameterizedTypeReference<ServicesResponseDTO<ClubDTO>> clubParameterTypeReference() {
         return new ParameterizedTypeReference<ServicesResponseDTO<ClubDTO>>() {};
    }
}

change the notation of the doGet method to public T doGet(UriComponentsBuilder builder, ParameterizedTypeReference<ServicesResponseDTO<T>> responseType ) throws ServiceException { and then call the doGet like this ClubDTO respuesta = client.doGet(builder, type);

instead of cleaner and expected ClubDTO respuesta = client.doGet(builder); and creating the type inside the doGet (I definetely don't want the service to know ParameterizedTypeReference<ServicesResponseDTO<ClubDTO>>, that should be just a client thing)

How to solve it?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.