This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 52100252e73ce76294ecebc2970a44f30e5895b0 Author: Maria Arias de Reyna <ariasdere...@redhat.com> AuthorDate: Mon May 11 19:01:21 2020 +0200 feat(geocoder): adding Nominatim support for geocoder Google is not only restricted licensed but also requires keys to use. Nominatim allows you to even use your own hosted geocoder server with your custom geolocation objects. --- components/camel-geocoder/pom.xml | 14 ++ .../geocoder/GeoCoderEndpointConfigurer.java | 8 + .../apache/camel/component/geocoder/geocoder.json | 22 ++- .../src/main/docs/geocoder-component.adoc | 61 +++--- .../camel/component/geocoder/GeoCoderEndpoint.java | 75 ++++++-- ...erProducer.java => GeoCoderGoogleProducer.java} | 9 +- .../geocoder/GeoCoderNominatimProducer.java | 205 +++++++++++++++++++++ .../camel/component/geocoder/GeoCoderType.java | 35 ++++ .../geocoder/GeoCoderNominatimAddressTest.java | 52 ++++++ .../component/geocoder/GeoCoderNominatimTest.java | 52 ++++++ 10 files changed, 480 insertions(+), 53 deletions(-) diff --git a/components/camel-geocoder/pom.xml b/components/camel-geocoder/pom.xml index 95b6c87..6f529ab 100644 --- a/components/camel-geocoder/pom.xml +++ b/components/camel-geocoder/pom.xml @@ -46,6 +46,20 @@ <artifactId>google-maps-services</artifactId> <version>${google-maps-services-version}</version> </dependency> + + <!-- Client library for JSON responses --> + <!-- Used with Nominatim --> + <dependency> + <groupId>com.jayway.jsonpath</groupId> + <artifactId>json-path</artifactId> + <version>${json-path-version}</version> + </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + <!-- testing --> <dependency> <groupId>org.apache.camel</groupId> diff --git a/components/camel-geocoder/src/generated/java/org/apache/camel/component/geocoder/GeoCoderEndpointConfigurer.java b/components/camel-geocoder/src/generated/java/org/apache/camel/component/geocoder/GeoCoderEndpointConfigurer.java index f6b4049..79fa7f9 100644 --- a/components/camel-geocoder/src/generated/java/org/apache/camel/component/geocoder/GeoCoderEndpointConfigurer.java +++ b/components/camel-geocoder/src/generated/java/org/apache/camel/component/geocoder/GeoCoderEndpointConfigurer.java @@ -46,7 +46,10 @@ public class GeoCoderEndpointConfigurer extends PropertyConfigurerSupport implem case "proxyHost": target.setProxyHost(property(camelContext, java.lang.String.class, value)); return true; case "proxyport": case "proxyPort": target.setProxyPort(property(camelContext, java.lang.Integer.class, value)); return true; + case "serverurl": + case "serverUrl": target.setServerUrl(property(camelContext, java.lang.String.class, value)); return true; case "synchronous": target.setSynchronous(property(camelContext, boolean.class, value)); return true; + case "type": target.setType(property(camelContext, org.apache.camel.component.geocoder.GeoCoderType.class, value)); return true; default: return false; } } @@ -68,7 +71,9 @@ public class GeoCoderEndpointConfigurer extends PropertyConfigurerSupport implem answer.put("proxyAuthUsername", java.lang.String.class); answer.put("proxyHost", java.lang.String.class); answer.put("proxyPort", java.lang.Integer.class); + answer.put("serverUrl", java.lang.String.class); answer.put("synchronous", boolean.class); + answer.put("type", org.apache.camel.component.geocoder.GeoCoderType.class); return answer; } @@ -103,7 +108,10 @@ public class GeoCoderEndpointConfigurer extends PropertyConfigurerSupport implem case "proxyHost": return target.getProxyHost(); case "proxyport": case "proxyPort": return target.getProxyPort(); + case "serverurl": + case "serverUrl": return target.getServerUrl(); case "synchronous": return target.isSynchronous(); + case "type": return target.getType(); default: return null; } } diff --git a/components/camel-geocoder/src/generated/resources/org/apache/camel/component/geocoder/geocoder.json b/components/camel-geocoder/src/generated/resources/org/apache/camel/component/geocoder/geocoder.json index a260c4c..9471b85 100644 --- a/components/camel-geocoder/src/generated/resources/org/apache/camel/component/geocoder/geocoder.json +++ b/components/camel-geocoder/src/generated/resources/org/apache/camel/component/geocoder/geocoder.json @@ -30,17 +30,19 @@ "headersOnly": { "kind": "parameter", "displayName": "Headers Only", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether to only enrich the Exchange with headers, and leave the body as-is." }, "language": { "kind": "parameter", "displayName": "Language", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "defaultValue": "en", "description": "The language to use." }, "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the [...] + "serverUrl": { "kind": "parameter", "displayName": "Server URL", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "URL to the geocoder server. Mandatory for Nominatim server." }, + "type": { "kind": "parameter", "displayName": "GeoCoding Type", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.geocoder.GeoCoderType", "enum": [ "NOMINATIM", "GOOGLE" ], "deprecated": false, "secret": false, "description": "Type of GeoCoding server. Supported Nominatim and Google." }, "basicPropertyBinding": { "kind": "parameter", "displayName": "Basic Property Binding", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": false, "description": "Whether the endpoint should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities" }, "synchronous": { "kind": "parameter", "displayName": "Synchronous", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "secret": false, "defaultValue": "false", "description": "Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported)." }, - "proxyAuthDomain": { "kind": "parameter", "displayName": "Proxy Auth Domain", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Domain for proxy NTML authentication" }, - "proxyAuthHost": { "kind": "parameter", "displayName": "Proxy Auth Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Optional host for proxy NTML authentication" }, - "proxyAuthMethod": { "kind": "parameter", "displayName": "Proxy Auth Method", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Authentication method for proxy, either as Basic, Digest or NTLM." }, - "proxyAuthPassword": { "kind": "parameter", "displayName": "Proxy Auth Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Password for proxy authentication" }, - "proxyAuthUsername": { "kind": "parameter", "displayName": "Proxy Auth Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Username for proxy authentication" }, - "proxyHost": { "kind": "parameter", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "The proxy host name" }, - "proxyPort": { "kind": "parameter", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "secret": false, "description": "The proxy port number" }, - "apiKey": { "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "To use google apiKey" }, - "clientId": { "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "To use google premium with this client id" }, - "clientKey": { "kind": "parameter", "displayName": "Client Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "To use google premium with this client key" } + "proxyAuthDomain": { "kind": "parameter", "displayName": "Proxy Auth Domain", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Proxy Authentication Domain to access Google GeoCoding server." }, + "proxyAuthHost": { "kind": "parameter", "displayName": "Proxy Auth Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Proxy Authentication Host to access Google GeoCoding server." }, + "proxyAuthMethod": { "kind": "parameter", "displayName": "Proxy Auth Method", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Authentication Method to Google GeoCoding server." }, + "proxyAuthPassword": { "kind": "parameter", "displayName": "Proxy Auth Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Proxy Password to access GeoCoding server." }, + "proxyAuthUsername": { "kind": "parameter", "displayName": "Proxy Auth Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Proxy Username to access GeoCoding server." }, + "proxyHost": { "kind": "parameter", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Proxy Host to access GeoCoding server." }, + "proxyPort": { "kind": "parameter", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "secret": false, "description": "Proxy Port to access GeoCoding server." }, + "apiKey": { "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "API Key to access Google. Mandatory for Google GeoCoding server." }, + "clientId": { "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "Client ID to access Google GeoCoding server." }, + "clientKey": { "kind": "parameter", "displayName": "Client Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": true, "description": "Client Key to access Google GeoCoding server." } } } diff --git a/components/camel-geocoder/src/main/docs/geocoder-component.adoc b/components/camel-geocoder/src/main/docs/geocoder-component.adoc index 77fc2b8..e350c98 100644 --- a/components/camel-geocoder/src/main/docs/geocoder-component.adoc +++ b/components/camel-geocoder/src/main/docs/geocoder-component.adoc @@ -12,9 +12,10 @@ *{component-header}* The Geocoder component is used for looking up geocodes (latitude and -longitude) for a given address, or reverse lookup. The component uses -the https://code.google.com/p/geocoder-java/[Java API for Google -Geocoder] library. +longitude) for a given address, or reverse lookup. + +The component uses either a hosted https://github.com/openstreetmap/Nominatim[Nominatim server] or +the https://code.google.com/p/geocoder-java/[Java API for Google Geocoder] library. Maven users will need to add the following dependency to their `pom.xml` for this component: @@ -76,7 +77,7 @@ with the following path and query parameters: |=== -=== Query Parameters (15 parameters): +=== Query Parameters (17 parameters): [width="100%",cols="2,5,^1,2",options="header"] @@ -85,18 +86,20 @@ with the following path and query parameters: | *headersOnly* (producer) | Whether to only enrich the Exchange with headers, and leave the body as-is. | false | boolean | *language* (producer) | The language to use. | en | String | *lazyStartProducer* (producer) | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and [...] +| *serverUrl* (producer) | URL to the geocoder server. Mandatory for Nominatim server. | | String +| *type* (producer) | Type of GeoCoding server. Supported Nominatim and Google. The value can be one of: NOMINATIM, GOOGLE | | GeoCoderType | *basicPropertyBinding* (advanced) | Whether the endpoint should use basic property binding (Camel 2.x) or the newer property binding with additional capabilities | false | boolean | *synchronous* (advanced) | Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported). | false | boolean -| *proxyAuthDomain* (proxy) | Domain for proxy NTML authentication | | String -| *proxyAuthHost* (proxy) | Optional host for proxy NTML authentication | | String -| *proxyAuthMethod* (proxy) | Authentication method for proxy, either as Basic, Digest or NTLM. | | String -| *proxyAuthPassword* (proxy) | Password for proxy authentication | | String -| *proxyAuthUsername* (proxy) | Username for proxy authentication | | String -| *proxyHost* (proxy) | The proxy host name | | String -| *proxyPort* (proxy) | The proxy port number | | Integer -| *apiKey* (security) | To use google apiKey | | String -| *clientId* (security) | To use google premium with this client id | | String -| *clientKey* (security) | To use google premium with this client key | | String +| *proxyAuthDomain* (proxy) | Proxy Authentication Domain to access Google GeoCoding server. | | String +| *proxyAuthHost* (proxy) | Proxy Authentication Host to access Google GeoCoding server. | | String +| *proxyAuthMethod* (proxy) | Authentication Method to Google GeoCoding server. | | String +| *proxyAuthPassword* (proxy) | Proxy Password to access GeoCoding server. | | String +| *proxyAuthUsername* (proxy) | Proxy Username to access GeoCoding server. | | String +| *proxyHost* (proxy) | Proxy Host to access GeoCoding server. | | String +| *proxyPort* (proxy) | Proxy Port to access GeoCoding server. | | Integer +| *apiKey* (security) | API Key to access Google. Mandatory for Google GeoCoding server. | | String +| *clientId* (security) | Client ID to access Google GeoCoding server. | | String +| *clientKey* (security) | Client Key to access Google GeoCoding server. | | String |=== // endpoint options: END @@ -105,15 +108,6 @@ with the following path and query parameters: == Exchange data format -Camel will deliver the body as a -`com.google.code.geocoder.model.GeocodeResponse` type. + - And if the address is `"current"` then the response is a String type -with a JSON representation of the current location. - -If the option `headersOnly` is set to `true` then the message body is -left as-is, and only headers will be added to the -Exchange. - == Message Headers [width="100%",cols="50%,50%",options="header",] @@ -147,6 +141,21 @@ Exchange. Notice not all headers may be provided depending on available data and mode in use (address vs latlng). +=== Body using a Nominatim Server + +Camel will deliver the body as a JSONv2 type. + +=== Body using a Google Server + +Camel will deliver the body as a +`com.google.code.geocoder.model.GeocodeResponse` type. + +And if the address is `"current"` then the response is a String type +with a JSON representation of the current location. + +If the option `headersOnly` is set to `true` then the message body is +left as-is, and only headers will be added to the +Exchange. + == Samples In the example below we get the latitude and longitude for Paris, France @@ -154,7 +163,7 @@ In the example below we get the latitude and longitude for Paris, France [source,java] ----------------------------------------- from("direct:start") - .to("geocoder:address:Paris, France") + .to("geocoder:address:Paris, France?type=NOMINATIM&serverUrl=https://nominatim.openstreetmap.org") ----------------------------------------- If you provide a header with the `CamelGeoCoderAddress` then that @@ -182,8 +191,8 @@ Which will log Location 285 Bedford Avenue, Brooklyn, NY 11211, USA is at lat/lng: 40.71412890,-73.96140740 and in country US -------------------------------------------------------------------------------------------------------------- -To get the current location you can use "current" as the address as -shown: +To get the current location using the Google GeoCoder, +you can use "current" as the address as shown: [source,java] ----------------------------------- diff --git a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java index 826098b..2c3babc 100644 --- a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java +++ b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java @@ -41,28 +41,44 @@ public class GeoCoderEndpoint extends DefaultEndpoint { private String latlng; @UriParam(defaultValue = "en") private String language = "en"; - @UriParam(label = "security", secret = true) + @UriParam(label = "security", secret = true, + description="Client ID to access Google GeoCoding server.") private String clientId; - @UriParam(label = "security", secret = true) + @UriParam(label = "security", secret = true, + description="Client Key to access Google GeoCoding server.") private String clientKey; - @UriParam(label = "security", secret = true) + @UriParam(label = "security", secret = true, + description="API Key to access Google. Mandatory for Google GeoCoding server.") private String apiKey; + @UriParam(description = "URL to the geocoder server. Mandatory for Nominatim server.", + displayName = "Server URL") + private String serverUrl; @UriParam private boolean headersOnly; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Host to access GeoCoding server.") private String proxyHost; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Port to access GeoCoding server.") private Integer proxyPort; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Authentication Method to Google GeoCoding server.") private String proxyAuthMethod; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Username to access GeoCoding server.") private String proxyAuthUsername; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Password to access GeoCoding server.") private String proxyAuthPassword; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Authentication Domain to access Google GeoCoding server.") private String proxyAuthDomain; - @UriParam(label = "proxy") + @UriParam(label = "proxy", + description="Proxy Authentication Host to access Google GeoCoding server.") private String proxyAuthHost; + @UriParam(displayName = "GeoCoding Type", + description = "Type of GeoCoding server. Supported Nominatim and Google.") + private GeoCoderType type; public GeoCoderEndpoint() { } @@ -73,7 +89,15 @@ public class GeoCoderEndpoint extends DefaultEndpoint { @Override public Producer createProducer() throws Exception { - return new GeoCoderProducer(this); + switch (getType()) + { + case NOMINATIM: + return new GeoCoderNominatimProducer(this); + case GOOGLE: + default: + // default to Google for backwards compatibility + return new GeoCoderGoogleProducer(this); + } } @Override @@ -236,7 +260,34 @@ public class GeoCoderEndpoint extends DefaultEndpoint { this.proxyAuthHost = proxyAuthHost; } - GeoApiContext createGeoApiContext() { + public GeoCoderType getType() { + if(type == null) { + type = GeoCoderType.GOOGLE; + } + return type; + } + + public void setType(GeoCoderType type) { + this.type = type; + } + + public void setType(String type) { + this.type = GeoCoderType.fromValue(type); + } + + public String getServerUrl() { + return serverUrl; + } + + public void setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + } + + /** + * Specific Google required + * @return + */ + protected GeoApiContext createGeoApiContext() { GeoApiContext.Builder builder = new GeoApiContext.Builder(); if (clientId != null) { builder = builder.enterpriseCredentials(clientId, clientKey); diff --git a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderProducer.java b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderGoogleProducer.java similarity index 97% rename from components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderProducer.java rename to components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderGoogleProducer.java index 72b8431..19dff2e 100644 --- a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderProducer.java +++ b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderGoogleProducer.java @@ -17,7 +17,6 @@ package org.apache.camel.component.geocoder; import java.util.Locale; - import com.google.maps.GeoApiContext; import com.google.maps.GeocodingApi; import com.google.maps.GeolocationApi; @@ -39,15 +38,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The GeoCoder producer. + * The GeoCoder producer for Google. */ -public class GeoCoderProducer extends DefaultProducer { - private static final Logger LOG = LoggerFactory.getLogger(GeoCoderProducer.class); +public class GeoCoderGoogleProducer extends DefaultProducer { + private static final Logger LOG = LoggerFactory.getLogger(GeoCoderGoogleProducer.class); private GeoCoderEndpoint endpoint; private GeoApiContext context; - public GeoCoderProducer(GeoCoderEndpoint endpoint) { + public GeoCoderGoogleProducer(GeoCoderEndpoint endpoint) { super(endpoint); this.endpoint = endpoint; } diff --git a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java new file mode 100644 index 0000000..0ed8a4c --- /dev/null +++ b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.geocoder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.support.DefaultProducer; +import org.apache.camel.util.StringHelper; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.RequestBuilder; +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; + +/** + * The GeoCoder producer for Nominatim. + */ +public class GeoCoderNominatimProducer extends DefaultProducer { + private static final Logger LOG = LoggerFactory.getLogger(GeoCoderNominatimProducer.class); + + private GeoCoderEndpoint endpoint; + + public GeoCoderNominatimProducer(GeoCoderEndpoint endpoint) { + super(endpoint); + this.endpoint = endpoint; + } + + @Override + public void process(Exchange exchange) throws Exception { + // headers take precedence + String address = exchange.getIn().getHeader(GeoCoderConstants.ADDRESS, String.class); + if (address == null) { + address = endpoint.getAddress(); + } + + String latlng = exchange.getIn().getHeader(GeoCoderConstants.LATLNG, String.class); + if (latlng == null) { + latlng = endpoint.getLatlng(); + } + + String geocoded = null; + + if (latlng != null) { + String lat = StringHelper.before(latlng, ","); + String lng = StringHelper.after(latlng, ","); + + LOG.debug("Geocode for lat/lng {}", latlng); + geocoded = query(lat, lng); + } else if (address != null) { + LOG.debug("Geocode for address {}", address); + geocoded = query(address); + } + + LOG.debug("Geocode response {}", geocoded); + + extractResult(geocoded, exchange); + } + + private String query(String dlat, String dlon) throws IOException { + + Map<String, String> params = new HashMap<String, String>(); + params.put("format", "jsonv2"); + params.put("lat", dlat); + params.put("lon", dlon); + + return queryForString("reverse", params); + } + + private String query(String address) throws IOException { + Map<String, String> params = new HashMap<String, String>(); + params.put("format", "jsonv2"); + params.put("addressdetails", "1"); + params.put("q", address); + params.put("limit", "1"); + + return queryForString("search", params); + } + + private String queryForString(String operation, Map<String, String> params) throws IOException { + String url = endpoint.getServerUrl(); + if (!url.endsWith("/")) { + url += "/"; + } + url += operation; + + final RequestBuilder builder = + RequestBuilder.get().setUri(url); + + for (Map.Entry<String, String> entry : params.entrySet()) { + builder.addParameter(entry.getKey(), entry.getValue()); + } + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpResponse resp = httpClient.execute(builder.build())) { + return EntityUtils.toString(resp.getEntity()); + } + } + } + + protected void extractResult(String place, Exchange exchange) { + if (!endpoint.isHeadersOnly()) { + exchange.getIn().setBody(place); + } + + if (place == null || place.isEmpty()) { + exchange.getIn().setHeader(GeoCoderConstants.STATUS, + GeocoderStatus.ZERO_RESULTS); + return; + } + exchange.getIn().setHeader(GeoCoderConstants.STATUS, GeocoderStatus.OK); + + if(place.startsWith("[") && place.endsWith("]")) { + place = place.substring(1, place.length() - 1); + } + + //additional details + final Configuration conf = + Configuration.defaultConfiguration() + .addOptions(Option.SUPPRESS_EXCEPTIONS); + final DocumentContext doc = JsonPath.using(conf).parse(place); + + + exchange.getIn().setHeader(GeoCoderConstants.ADDRESS, + doc.read("$['display_name']")); + + // just grab the first element and its lat and lon + setLatLngToExchangeHeader( + doc.read("$['lat']"), + doc.read("$['lon']"), + exchange); + + extractCountry(doc, exchange.getIn()); + extractCity(doc, exchange.getIn()); + extractPostalCode(doc, exchange.getIn()); + extractRegion(doc, exchange.getIn()); + } + + private void setLatLngToExchangeHeader(String resLat, String resLng, Exchange exchange) { + exchange.getIn().setHeader(GeoCoderConstants.LAT, formatLatOrLon(resLat)); + exchange.getIn().setHeader(GeoCoderConstants.LNG, formatLatOrLon(resLng)); + String resLatlng = formatLatOrLon(resLat) + ", " + formatLatOrLon(resLng); + exchange.getIn().setHeader(GeoCoderConstants.LATLNG, resLatlng); + } + + private void extractCountry(DocumentContext doc, Message in) { + String code = + doc.read("$['address']['country_code']"); + if (code != null) { + code = code.toUpperCase(); + } + in.setHeader(GeoCoderConstants.COUNTRY_SHORT, code); + in.setHeader(GeoCoderConstants.COUNTRY_LONG, + doc.read("$['address']['country']")); + } + + private void extractCity(DocumentContext doc, Message in) { + in.setHeader(GeoCoderConstants.CITY, + doc.read("$['address']['city']")); + } + + private void extractPostalCode(DocumentContext doc, Message in) { + in.setHeader(GeoCoderConstants.POSTAL_CODE, + doc.read("$['address']['postcode']")); + } + + private void extractRegion(DocumentContext doc, Message in) { + String code = + doc.read("$['address']['state_code']"); + if (code != null) { + code = code.toUpperCase(); + } + in.setHeader(GeoCoderConstants.REGION_CODE, code); + in.setHeader(GeoCoderConstants.REGION_NAME, + doc.read("$['address']['state']")); + } + + private String formatLatOrLon(String value) { + return String.format(Locale.ENGLISH, "%.8f", + Double.parseDouble(value)); + } +} diff --git a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderType.java b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderType.java new file mode 100644 index 0000000..3a822ec --- /dev/null +++ b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderType.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.geocoder; + +public enum GeoCoderType { + NOMINATIM, + GOOGLE; + + public String value() { + return name(); + } + + public static GeoCoderType fromValue(String v) { + if (v == null || v.isEmpty()) { + //Default to Google for backwards compatibility + return GOOGLE; + } + + return valueOf(v); + } +} \ No newline at end of file diff --git a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java new file mode 100644 index 0000000..5705edb --- /dev/null +++ b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.geocoder; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class GeoCoderNominatimAddressTest extends CamelTestSupport { + + @Test + public void testGeoCoder() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(1); + mock.expectedHeaderReceived(GeoCoderConstants.COUNTRY_SHORT, "ES"); + mock.expectedHeaderReceived(GeoCoderConstants.CITY, "Sevilla"); + + // the address header overrides the endpoint configuration + template.sendBody("direct:start", "Test"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() { + from("direct:start") + .to("geocoder:address:calle marie curie, sevilla, sevilla?type=NOMINATIM&serverUrl=RAW(https://nominatim.openstreetmap.org)") + .to("log:result") + .log("Location ${header.CamelGeocoderAddress} is at lat/lng: ${header.CamelGeocoderLatlng}" + + " and in city ${header.CamelGeoCoderCity} in country ${header.CamelGeoCoderCountryLong}") + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java new file mode 100644 index 0000000..3dd90b9 --- /dev/null +++ b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.camel.component.geocoder; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class GeoCoderNominatimTest extends CamelTestSupport { + + @Test + public void testGeoCoder() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(1); + mock.expectedHeaderReceived(GeoCoderConstants.COUNTRY_SHORT, "ES"); + mock.expectedHeaderReceived(GeoCoderConstants.CITY, "Sevilla"); + + // the address header overrides the endpoint configuration + template.sendBody("direct:start", "Test"); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() { + from("direct:start") + .to("geocoder:latlng:37.38619,-5.99255?type=NOMINATIM&serverUrl=RAW(https://nominatim.openstreetmap.org)") + .to("log:result") + .log("Location ${header.CamelGeocoderAddress} is at lat/lng: ${header.CamelGeocoderLatlng}" + + " and in city ${header.CamelGeoCoderCity} in country ${header.CamelGeoCoderCountryLong}") + .to("mock:result"); + } + }; + } +}