uk.co.caprica.brue.okhttp.service.bridge.AbstractBridgeService.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.caprica.brue.okhttp.service.bridge.AbstractBridgeService.java

Source

/*
 * Copyright 2014 Caprica Software Limited
 * (http://www.capricasoftware.co.uk)
 *
 * This file is part of Brue.
 *
 * Brue 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 3 of the License, or (at your option)
 * any later version.
 *
 * Brue 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 Brue.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.co.caprica.brue.okhttp.service.bridge;

import java.io.IOException;

import uk.co.caprica.brue.core.domain.bridge.authorisation.Authorisation;
import uk.co.caprica.brue.core.domain.bridge.result.ResultList;
import uk.co.caprica.brue.core.domain.bridge.result.Results;
import uk.co.caprica.brue.core.service.bridge.BridgeErrorResultException;
import uk.co.caprica.brue.core.service.bridge.BridgeIOException;
import uk.co.caprica.brue.core.service.bridge.BridgeResponseException;
import uk.co.caprica.brue.core.settings.bridge.BridgeSettings;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;

// FIXME push httpClient to a common static base class, e.g. to share with DiscoveryService

/**
 * Base implementation for a Philips Hue Bridge service that uses HTTP to communicate with the bridge.
 * <p>
 * This is a simple HTTP client wrapper with specific behaviour to simplify and optimise interactions with a Philips Hue bridge.
 */
abstract class AbstractBridgeService {

    /**
     * Shared HTTP Client.
     * <p>
     * Once created, this client is completely thread-safe and can safely be re-used.
     */
    private static final OkHttpClient httpClient = new OkHttpClient();

    /**
     * Shared JSON Object Mapper.
     * <p>
     * Once created and configured, this object mapper is completely thread-safe and can be safely re-used.
     */
    private static final ObjectMapper objectMapper = new ObjectMapper() {
        {
            registerModule(new GuavaModule());
        } // FIXME don't think i need this anymore
    };

    /**
     *
     */
    private static final String TEMPLATE_BRIDGE_URL = "http://%s/api";

    /**
     * Standard HTTP Accept header name.
     */
    private static final String HEADER_ACCEPT = "Accept";

    /**
     * Standard media type for JSON requests and responses.
     */
    private static final String MEDIA_TYPE_JSON = "application/json";

    /**
     *
     */
    private final BridgeSettings bridgeSettings;

    /**
     * Resource path (part of the URL) for this service.
     */
    private final String resourcePath;

    /**
     *
     *
     * @param bridgeSettings
     */
    protected AbstractBridgeService(BridgeSettings bridgeSettings) {
        this(bridgeSettings, null);
    }

    /**
     *
     *
     * @param bridgeSettings
     * @param resourcePath
     */
    protected AbstractBridgeService(BridgeSettings bridgeSettings, String resourcePath) {
        this.bridgeSettings = bridgeSettings;
        this.resourcePath = resourcePath;
    }

    /**
     *
     *
     * @param authorisation
     * @return
     */
    protected final Results authoriseResource(Authorisation authorisation) {
        Request request = new Request.Builder().url(bridgeUrl()).post(requestBody(authorisation))
                .addHeader(HEADER_ACCEPT, MEDIA_TYPE_JSON).build();
        return executeRequest(request);
    }

    /**
     * Perform an HTTP GET request to get a resource.
     *
     * @param resourceUrl
     * @param responseType
     * @param <T>
     * @return
     */
    protected final <T> T getResource(String resourceUrl, Class<T> responseType) {
        return getResource(resourceUrl, typeOf(responseType));
    }

    /**
     *
     *
     * @param resourceUrl
     * @param responseType
     * @param <T>
     * @return
     */
    protected final <T> T getResource(String resourceUrl, TypeReference<T> responseType) {
        return getResource(resourceUrl, typeOf(responseType));
    }

    /**
     * Perform an HTTP POST request to create a resource.
     *
     * @param resourceUrl
     * @param object
     * @return
     */
    protected final Results createResource(String resourceUrl, Object object) {
        Request request = new Request.Builder().url(resourceUrl).post(requestBody(object))
                .addHeader(HEADER_ACCEPT, MEDIA_TYPE_JSON).build();
        return executeRequest(request);
    }

    /**
     * Perform an HTTP PUT request to update a resource.
     *
     * @param resourceUrl
     * @param object
     * @return
     */
    protected final Results updateResource(String resourceUrl, Object object) {
        Request request = new Request.Builder().url(resourceUrl).put(requestBody(object))
                .addHeader(HEADER_ACCEPT, MEDIA_TYPE_JSON).build();
        return executeRequest(request);
    }

    /**
     * Perform an HTTP DELETE request to delete a resource.
     *
     * @param resourceUrl
     */
    protected final Results deleteResource(String resourceUrl) {
        Request request = new Request.Builder().url(resourceUrl).delete().build();
        return executeRequest(request);
    }

    /**
     *
     *
     * @return
     */
    private String bridgeUrl() {
        return String.format(TEMPLATE_BRIDGE_URL, bridgeSettings.host());
    }

    /**
     *
     *
     * @return
     */
    protected final String resourceUrl() {
        return String.format("%s/%s/%s", bridgeUrl(), bridgeSettings.username(), resourcePath);
    }

    /**
     *
     *
     * @param requestPath
     * @return
     */
    protected final String resourceUrl(Object requestPath, Object... morePath) {
        String result = String.format("%s/%s", resourceUrl(), requestPath.toString());
        if (morePath != null) {
            StringBuilder sb = new StringBuilder(result.length() + 20);
            sb.append(result);
            for (Object o : morePath) {
                sb.append('/').append(o);
            }
            result = sb.toString();
        }
        return result;
    }

    /**
     *
     *
     * @param type
     * @return
     */
    private JavaType typeOf(Class<?> type) {
        return objectMapper.getTypeFactory().constructType(type);
    }

    /**
     *
     *
     * @param type
     * @return
     */
    private JavaType typeOf(TypeReference<?> type) {
        return objectMapper.getTypeFactory().constructType(type);
    }

    /**
     *
     *
     * @param resourceUrl
     * @param responseType
     * @param <T>
     * @return
     */
    private <T> T getResource(String resourceUrl, JavaType responseType) {
        Request request = new Request.Builder().url(resourceUrl).addHeader(HEADER_ACCEPT, MEDIA_TYPE_JSON).build();
        return executeRequest(request, responseType);
    }

    /**
     *
     *
     * @param object
     * @return
     */
    private RequestBody requestBody(Object object) {
        try {
            String body = objectMapper.writer().writeValueAsString(object);
            return RequestBody.create(MediaType.parse(MEDIA_TYPE_JSON), body);
        } catch (IOException e) {
            throw new BridgeIOException(e);
        }
    }

    /**
     * Execute a request, synchronously.
     *
     * @param request
     * @param responseType
     * @param <T>
     * @return
     */
    private <T> T executeRequest(Request request, JavaType responseType) {
        try {
            Response response = httpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                if (responseType != null) {
                    return response(response, responseType);
                } else {
                    return null;
                }
            } else {
                throw new BridgeResponseException(response.code(), response.message());
            }
        } catch (IOException e) {
            throw new BridgeIOException(e);
        }
    }

    /**
     *
     *
     * @param request
     * @return
     */
    private Results executeRequest(Request request) {
        ResultList results = executeRequest(request, typeOf(ResultList.class));
        return results.results();
    }

    /**
     * Extract a domain object from an HTTP response body.
     * <p>
     * Note that the response body must be explicitly closed (and in this case is done so by using the try-with-resources
     * approach).
     *
     * @param response
     * @param responseType
     * @param <T>
     * @return
     */
    private <T> T response(Response response, JavaType responseType) {
        try (ResponseBody responseBody = response.body()) {
            T result = mapResponse(responseBody.string(), responseType);
            return result;
        } catch (IOException e) {
            throw new BridgeIOException(e);
        }
    }

    /**
     * Map a JSON response to a domain object.
     * <p>
     * This method has been factored out to simplify the unfortunate necessary handling of checked IOException.
     *
     * @param body
     * @param responseType
     * @param <T>
     * @return
     */
    private <T> T mapResponse(String body, JavaType responseType) {
        try {
            System.out.println("body=" + body);

            T result = objectMapper.reader().withType(responseType).readValue(body);
            return result;
        } catch (IOException e) {
            try {
                ResultList result = objectMapper.reader().withType(ResultList.class).readValue(body);
                throw new BridgeErrorResultException(result.results());
            } catch (IOException f) {
                // Failed to unmarshal the error response, so throw the *original* exception
                throw new BridgeIOException(e);
            }
        }
    }
}