org.yql4j.impl.HttpComponentsYqlClient.java Source code

Java tutorial

Introduction

Here is the source code for org.yql4j.impl.HttpComponentsYqlClient.java

Source

/*
 * Copyright (C) 2014 Philipp Nanz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.yql4j.impl;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthException;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yql4j.ResultFormat;
import org.yql4j.YqlClient;
import org.yql4j.YqlException;
import org.yql4j.YqlQuery;
import org.yql4j.YqlResult;
import org.yql4j.types.ErrorType;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.google.common.base.Stopwatch;

/**
 * This class implements the {@link YqlClient} contract using the <a
 * href="http://hc.apache.org/httpcomponents-client-ga/">Apache
 * HttpComponents HttpClient</a> for transport.
 */
public class HttpComponentsYqlClient implements YqlClient {

    protected final Logger logger = LoggerFactory.getLogger(HttpComponentsYqlClient.class);

    protected static final String SERVICE_URL_PUBLIC = "http://query.yahooapis.com/v1/public/yql";
    protected static final String SERVICE_URL_OAUTH = "http://query.yahooapis.com/v1/yql";

    protected CloseableHttpClient httpClient;
    protected ObjectMapper jsonMapper;
    protected ObjectMapper xmlMapper;

    /**
     * Default constructor.
     */
    public HttpComponentsYqlClient() {
        this(HttpClients.createDefault());
    }

    /**
     * Constructor.
     * 
     * @param httpClient
     *            the {@link CloseableHttpClient} instance to use
     */
    public HttpComponentsYqlClient(CloseableHttpClient httpClient) {
        checkNotNull(httpClient);
        this.httpClient = httpClient;
        this.jsonMapper = createJsonMapper();
        this.xmlMapper = createXmlMapper();
    }

    /* 
     * (non-Javadoc)
     * 
     * @see org.yql4j.YqlClient#getJsonObjectMapper()
     */
    @Override
    public ObjectMapper getJsonObjectMapper() {
        return jsonMapper;
    }

    /* 
     * (non-Javadoc)
     * 
     * @see org.yql4j.YqlClient#getXmlObjectMapper()
     */
    @Override
    public ObjectMapper getXmlObjectMapper() {
        return xmlMapper;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.yql4j.YqlClient#query(org.yql4j.YqlQuery)
     */
    @Override
    public YqlResult query(YqlQuery query) throws YqlException {
        checkNotNull(query);
        try {
            HttpUriRequest request = createHttpRequest(query);
            request = signHttpRequest(request, query);

            Stopwatch timer = Stopwatch.createStarted();
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                logger.debug("YQL query (URL=" + query.toUri() + ") took "
                        + timer.stop().elapsed(TimeUnit.MILLISECONDS) + "ms");

                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    HttpEntity entity = response.getEntity();
                    Map<String, String> headers = new HashMap<>();
                    for (Header header : response.getAllHeaders()) {
                        headers.put(header.getName(), header.getValue());
                    }
                    return new YqlResult(EntityUtils.toString(entity), headers,
                            query.getFormat() != null ? query.getFormat() : ResultFormat.XML,
                            getAppropriateMapper(query));
                } else if (isClientError(response)) {
                    HttpEntity entity = response.getEntity();
                    ObjectMapper mapper = getAppropriateMapper(query);
                    ErrorType error = mapper.readValue(EntityUtils.toString(entity), ErrorType.class);
                    throw new YqlException(
                            "Failed to execute YQL query (URL=" + query.toUri() + "): " + error.getDescription());
                } else {
                    throw new YqlException("Failed to execute YQL query (URL=" + query.toUri()
                            + "): Received unexpected status code " + response.getStatusLine().getStatusCode());
                }
            }
        } catch (ParseException | OAuthException | IOException e) {
            throw new YqlException("Failed to execute YQL query (URL=" + query.toUri() + "): " + e.getMessage(), e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.io.Closeable#close()
     */
    @Override
    public void close() throws IOException {
        httpClient.close();
    }

    /**
     * Creates the Jackson {@link ObjectMapper} instance to use for XML
     * processing.
     * <p>
     * Applies only if <code>format=json</code>.
     * 
     * @return a Jackson {@link ObjectMapper} instance
     */
    protected ObjectMapper createJsonMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
        mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        return mapper;
    }

    /**
     * Creates the Jackson {@link ObjectMapper} instance to use for XML
     * processing.
     * <p>
     * Applies only if <code>format=xml</code>.
     * 
     * @return a Jackson {@link ObjectMapper} instance
     */
    protected ObjectMapper createXmlMapper() {
        return new XmlMapper();
    }

    /**
     * Create the HTTP request to execute.
     * 
     * @param query
     *            the query specification
     * @return the HTTP request
     */
    protected HttpUriRequest createHttpRequest(YqlQuery query) {
        URI uri = query.toUri();
        logger.debug("YQL query URL: " + uri.toString());
        return new HttpGet(uri);
    }

    /**
     * Signs the HTTP request using OAuth.
     * 
     * @param request
     *            the HTTP request
     * @param query
     *            the query specification
     * @return the signed HTTP request
     * @throws OAuthException
     *             if an error occurred
     */
    protected HttpUriRequest signHttpRequest(HttpUriRequest request, YqlQuery query) throws OAuthException {
        boolean oAuth = (query.getConsumerKey() != null) && (query.getConsumerSecret() != null);
        if (oAuth) {
            // We are only signing the request at this point
            CommonsHttpOAuthConsumer consumer = new CommonsHttpOAuthConsumer(query.getConsumerKey(),
                    query.getConsumerSecret());
            consumer.sign(request);
        }
        return request;
    }

    /**
     * Returns whether the HTTP response is in an error state caused by the
     * client.
     * 
     * @param response
     *            the HTTP response
     * @return <code>true</code> if response is in error caused by the client
     */
    protected boolean isClientError(HttpResponse response) {
        int sc = response.getStatusLine().getStatusCode();
        return sc >= 400 && sc < 500;
    }

    /**
     * Returns the {@link ObjectMapper} appropriate for the given query.
     * 
     * @param query
     *            the query specification
     * @return the appropriate Jackson {@link ObjectMapper}
     */
    protected ObjectMapper getAppropriateMapper(YqlQuery query) {
        ResultFormat format = query.getFormat();
        if (ResultFormat.JSON.equals(format)) {
            return jsonMapper;
        }
        return xmlMapper;
    }
}