com.tomtom.speedtools.services.lbs.route.implementation.TomTomLbsRouteEngineRouteEngineActorImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.tomtom.speedtools.services.lbs.route.implementation.TomTomLbsRouteEngineRouteEngineActorImpl.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.route.implementation;

import akka.dispatch.Futures;
import com.tomtom.speedtools.geometry.GeoPoint;
import com.tomtom.speedtools.services.lbs.AuthorizationException;
import com.tomtom.speedtools.services.lbs.LbsProperties;
import com.tomtom.speedtools.services.lbs.route.RouteEngineResponse;
import com.tomtom.speedtools.time.UTCTime;
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.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.concurrent.Future;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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;
import java.util.concurrent.TimeoutException;

/**
 * Implementation of a {@link com.tomtom.speedtools.services.lbs.route.RouteEngine} using the TomTom LBS Router.
 */
final class TomTomLbsRouteEngineRouteEngineActorImpl implements TomTomLbsRouteEngineActor {
    private static final Logger LOG = LoggerFactory.getLogger(TomTomLbsRouteEngineRouteEngineActorImpl.class);

    private static final String LBS_OUTPUT_TYPE = "xml";
    private static final String LBS_ROUTE_TYPE = "Quickest";
    private static final String LBS_OPTIONS_TRAFFIC = ";avoidTraffic=true;includeTraffic=true";
    private static final String LBS_OPTIONS_OTHERS = ";day=today;time=now;iqRoutes=2;map=basic;pathPoints=1";
    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    @Nonnull
    private final String name;
    @Nonnull
    private final LbsProperties lbsProperties;

    TomTomLbsRouteEngineRouteEngineActorImpl(@Nonnull final String name,
            @Nonnull final LbsProperties lbsProperties) {
        assert name != null;
        assert lbsProperties != null;
        this.name = name;
        this.lbsProperties = lbsProperties;
    }

    @Override
    @Nonnull
    public Future<RouteEngineResponse> route(@Nonnull final GeoPoint from, @Nonnull final GeoPoint to,
            @Nullable final DateTime executeBefore) {
        assert from != null;
        assert to != null;

        // TODO ST-3: For as long as this is a route actor, we shall prevent crashing this actor at all cost.
        try {
            LOG.debug("route: {} Request, from={}, to={}, traffic={}", name, from, to,
                    lbsProperties.isTrafficEnabled());
            final DateTime now = UTCTime.now();
            if ((executeBefore == null) || now.isBefore(executeBefore)) {
                final String url = getBaseQueryString(from, to);
                final TomTomLbsRouteEngineResponse lbsResponse;
                lbsResponse = executeLbsQuery(url);
                LOG.debug("route: {} Success, from={}, to={}, response={}", name, from, to, lbsResponse);

                final RouteEngineResponse routeEngineResponse = lbsResponse.convertToRouteEngineResponse();
                return Futures.successful(routeEngineResponse);
            } else {
                final String message = "Expected execution before: " + executeBefore.toString(DATE_FORMAT)
                        + " but started at: " + now.toString(DATE_FORMAT);
                LOG.debug("route: {} Too busy, from={}, to={}, {}", name, from, to, message);
                return Futures.failed(new TimeoutException(message));
            }
        } catch (final AuthorizationException e) {
            return Futures.failed(e);
        } catch (final ServiceUnavailableException e) {
            return Futures.failed(e);
        } catch (final IOException e) {
            return Futures.failed(e);
        } catch (final Throwable e) {

            // Catch all to make sure the actor won't die and takes its parent down.
            return Futures.failed(new IllegalStateException(e));
        }
    }

    /**
     * Convenience method: return a fully qualified URL to which parameters can be appended.
     *
     * @param from Departure point.
     * @param to   Arrival point.
     * @return From-to URL.
     */
    @Nonnull
    private String getBaseQueryString(@Nonnull final GeoPoint from, @Nonnull final GeoPoint to) {
        assert from != null;
        assert to != null;
        return lbsProperties.getRouteUrl1() + '/' + toTomTomLbsUriString(from, to) + '/' + LBS_ROUTE_TYPE + '/'
                + LBS_OUTPUT_TYPE + '/' + lbsProperties.getApiKey()
                + (lbsProperties.isTrafficEnabled() ? LBS_OPTIONS_TRAFFIC : "") + LBS_OPTIONS_OTHERS;
    }

    /**
     * Perform actual LBS HTTP query.
     *
     * @param queryUrl URL to query.
     * @return Valid route engine response 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 TomTomLbsRouteEngineResponse executeLbsQuery(@Nonnull final String queryUrl)
            throws AuthorizationException, ServiceUnavailableException, IOException {
        assert queryUrl != null;
        LOG.trace("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.info("executeLbsQuery: routing service failure, url={}, status={} ", queryUrl, get.getStatusLine());
        }
        final TomTomLbsRouteEngineResponse response;
        switch (status) {

        case HttpStatus.SC_OK:
            try {
                response = unmarshalRouterResponseBody(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_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_NO_CONTENT:
            // Fall through.
        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 route engine, 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 route response object or null on error.
     * @throws JAXBException Thrown if unmarshalling fails.
     */
    @Nonnull
    private static TomTomLbsRouteEngineResponse unmarshalRouterResponseBody(@Nonnull final InputStream stream)
            throws JAXBException {
        assert stream != null;
        final JAXBContext context = JAXBContext.newInstance(TomTomLbsRouteEngineResponse.class);
        final Unmarshaller unmarshaller = context.createUnmarshaller();
        final TomTomLbsRouteEngineResponse response = (TomTomLbsRouteEngineResponse) unmarshaller.unmarshal(stream);
        if (response == null) {
            throw new JAXBException("Response cannot be unmarshalled");
        }
        return response;
    }

    /**
     * Return a LBS formatted route request string.
     *
     * @return Route request string.
     */
    @Nonnull
    private static String toTomTomLbsUriString(@Nonnull final GeoPoint from, @Nonnull final GeoPoint to) {
        assert from != null;
        assert to != null;
        return encodeGeoPoint(from) + ':' + encodeGeoPoint(to);
    }

    @Nonnull
    private static String encodeGeoPoint(@Nonnull final GeoPoint point) {
        assert point != null;
        final String result = point.getLat().toString() + ',' + point.getLon();
        return UrlParameterBuilder.encode(result);
    }
}