com.tomtom.speedtools.services.lbs.geocode.implementation.TomTomLbsGeoCodeEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.tomtom.speedtools.services.lbs.geocode.implementation.TomTomLbsGeoCodeEngine.java

Source

/*
 * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com).
 *
 * 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 com.tomtom.speedtools.services.lbs.geocode.implementation;

import com.tomtom.speedtools.geometry.GeoRectangle;
import com.tomtom.speedtools.services.lbs.AuthorizationException;
import com.tomtom.speedtools.services.lbs.LbsProperties;
import com.tomtom.speedtools.services.lbs.geocode.GeoCodeEngine;
import com.tomtom.speedtools.services.lbs.geocode.GeoCodeEngineResponse;
import com.tomtom.speedtools.urls.UrlParameterBuilder;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.naming.ServiceUnavailableException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.InputStream;

/**
 * Implementation of a {@link GeoCodeEngine} using the TomTom LBS
 * Geocoder.
 */
public final class TomTomLbsGeoCodeEngine implements GeoCodeEngine {
    private static final Logger LOG = LoggerFactory.getLogger(TomTomLbsGeoCodeEngine.class);

    // LBS host parameters.
    private static final String LBS_GEOCODE_FORMAT = "xml";

    // LBS properties.
    @Nonnull
    private final LbsProperties lbsProperties;

    @Inject
    public TomTomLbsGeoCodeEngine(@Nonnull final LbsProperties lbsProperties) {
        assert lbsProperties != null;
        this.lbsProperties = lbsProperties;
    }

    @Nonnull
    @Override
    public GeoCodeEngineResponse query(@Nonnull final String query)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert query != null;
        final String url = getBaseQueryString(query);
        return executeLbsQuery(url);
    }

    @Nonnull
    @Override
    public GeoCodeEngineResponse query(@Nonnull final String query, @Nonnull final String parameters)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert query != null;
        assert parameters != null;
        final String url = getBaseQueryString(query) + ';' + parameters;
        return executeLbsQuery(url);
    }

    @Nonnull
    @Override
    public GeoCodeEngineResponse query(@Nonnull final String query, @Nonnull final GeoRectangle boundingBox)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert query != null;
        assert boundingBox != null;
        final String url = getBaseQueryString(query) + ";bbox=" + toTomTomLbsUriString(boundingBox);
        return executeLbsQuery(url);
    }

    @Nonnull
    @Override
    public GeoCodeEngineResponse query(@Nonnull final String query, @Nonnull final GeoRectangle boundingBox,
            @Nullable final String parameters)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert query != null;
        assert boundingBox != null;

        // Escape.
        if (parameters == null) {
            return query(query, boundingBox);
        }
        final String url = getBaseQueryString(query) + ";bbox=" + toTomTomLbsUriString(boundingBox) + ';'
                + parameters;
        return executeLbsQuery(url);
    }

    /**
     * Convenience method: return a fully qualified URL to which parameters can be appended.
     *
     * @param query Query.
     * @return Query URL.
     */
    @Nonnull
    private String getBaseQueryString(@Nonnull final String query) {
        assert query != null;
        return lbsProperties.getGeoCodeUrl1() + '/' + UrlParameterBuilder.encode(query) + '/' + LBS_GEOCODE_FORMAT
                + '/' + lbsProperties.getApiKey();
    }

    /**
     * Perform actual LBS HTTP query.
     *
     * @param queryUrl URL to query.
     * @return Valid GeoCoderResponse object.
     * @throws AuthorizationException      Thrown in case API key is rejected by server
     * @throws ServiceUnavailableException Thrown if service is not available.
     * @throws IOException                 Thrown if GET could not be executed for some reason.
     */
    @SuppressWarnings("OverlyBroadThrowsClause")
    @Nonnull
    private static GeoCodeEngineResponse executeLbsQuery(@Nonnull final String queryUrl)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert queryUrl != null;
        LOG.debug("executeLbsQuery: url={}", queryUrl);
        final GetMethod get = new GetMethod(queryUrl);
        final int status = new HttpClient().executeMethod(get);

        // Log error message.
        if (status != HttpStatus.SC_OK) {
            LOG.warn("executeLbsQuery: geocoding service failure, url={}, status={} ", queryUrl,
                    get.getStatusLine());
        }
        final GeoCodeEngineResponse response;
        switch (status) {

        case HttpStatus.SC_OK:
            try {
                response = unmarshalGeoCoderResponseBody(get.getResponseBodyAsStream());
            } catch (final JAXBException e) {
                LOG.warn("executeLbsQuery: cannot unmarshal response", e);
                throw new IOException("Cannot unmarshal response", e);
            } finally {
                get.releaseConnection();
            }
            assert response != null;
            break;

        case HttpStatus.SC_NO_CONTENT:
            response = new GeoCodeEngineResponse();
            break;

        case HttpStatus.SC_UNAUTHORIZED:
            // Fall through.
        case HttpStatus.SC_METHOD_NOT_ALLOWED:
            // Fall through.
        case HttpStatus.SC_NOT_ACCEPTABLE:
            // Fall through.
        case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
            throw new AuthorizationException();

        case HttpStatus.SC_INTERNAL_SERVER_ERROR:
            // Fall through.
        case HttpStatus.SC_SERVICE_UNAVAILABLE:
            // Fall through.
        case HttpStatus.SC_GATEWAY_TIMEOUT:
            // Fall through.
        case HttpStatus.SC_INSUFFICIENT_STORAGE:
            throw new ServiceUnavailableException();

        default:
            throw new IOException("Cannot call geocoder, status=" + status + " (" + get.getStatusLine() + ')');
        }
        assert response != null;
        return response;
    }

    /**
     * Do the heavy lifting of binding the input stream to the JAX-B annotated objects.
     *
     * @param stream Stream to read from.
     * @return Valid GeoCoderResponse object or null on error.
     * @throws JAXBException If an error occurs unmarshalling.
     */
    @Nullable
    private static GeoCodeEngineResponse unmarshalGeoCoderResponseBody(@Nonnull final InputStream stream)
            throws JAXBException {
        assert stream != null;
        final JAXBContext context = JAXBContext.newInstance(GeoCodeEngineResponse.class);
        final Unmarshaller unmarshaller = context.createUnmarshaller();
        final GeoCodeEngineResponse response = (GeoCodeEngineResponse) unmarshaller.unmarshal(stream);
        return response;
    }

    /**
     * Return a LBS formatted boundary box string.
     *
     * @return Boundary box formatted for use with TomTom LBS.
     */
    @Nonnull
    private static String toTomTomLbsUriString(@Nonnull final GeoRectangle rect) {
        assert rect != null;
        return rect.getSouthWest().getLat().toString() + ',' + rect.getSouthWest().getLon() + ','
                + rect.getNorthEast().getLat() + ',' + rect.getNorthEast().getLon();
    }
}