class GraphiteClient {

    public List<GraphiteResult> getData(long from, long until, int intervalLength, String target) {

        notNull(from, "from can't be null");
        notNull(until, "until can't be null");
        notNull(target, "target can't be null");

        // Explicitly specify maxDataPoints in case the number of metrics per day required exceeds server's default maxDataPoints limit
        val maxDataPoints = calculateMaxDataPointsPerDay(from, until, intervalLength);
        val uri = String.format(baseUri + FETCH_METRICS_PATH, from, until, maxDataPoints, target);
        log.debug("Sending query to Graphite target: {}", uri);

        //FIXME x-org-id is a mandatory header for metric tank.
        // In future, we would like to make it configurable as the only difference between graphite client and metric tank client is x-org-id header.
        val headers = Collections.singletonMap("x-org-id", "1");
        Content content;
        try {
            content = httpClient.get(uri, headers);
        } catch (IOException e) {
            val message = String.format("Encountered IOException while querying Metrictank target '%s': httpMethod=GET, uri=%s, message=%s",
                    target,
                    uri,
                    e.getMessage());
            throw new GraphiteClientException(message, e);
        }

        List<GraphiteResult> results;
        try {
            results = Arrays.asList(objectMapper.readValue(content.asBytes(), GraphiteResult[].class));
        } catch (IOException e) {
            throw new GraphiteClientException(String.format("IOException while parsing response from Metrictank target: %s", target), e);
        }
        return results;
    }

}