/src/main/java/com/atlassian/jconnect/jira/customfields/LocationLikeQueryFactory.java
Java | 215 lines | 187 code | 23 blank | 5 comment | 23 complexity | 64adf3819031ee52bdb855ad8a8fd3cc MD5 | raw file
- package com.atlassian.jconnect.jira.customfields;
- import com.atlassian.jira.issue.customfields.converters.DoubleConverter;
- import com.atlassian.jira.jql.operand.JqlOperandResolver;
- import com.atlassian.jira.jql.operand.QueryLiteral;
- import com.atlassian.jira.jql.query.ClauseQueryFactory;
- import com.atlassian.jira.jql.query.QueryCreationContext;
- import com.atlassian.jira.jql.query.QueryFactoryResult;
- import com.atlassian.jira.security.JiraAuthenticationContext;
- import com.atlassian.jira.util.NotNull;
- import com.atlassian.jira.util.json.JSONArray;
- import com.atlassian.jira.util.json.JSONException;
- import com.atlassian.jira.util.json.JSONObject;
- import com.atlassian.query.clause.TerminalClause;
- import com.atlassian.query.operator.Operator;
- import com.google.common.collect.Lists;
- import org.apache.commons.httpclient.HttpClient;
- import org.apache.commons.httpclient.HttpMethod;
- import org.apache.commons.httpclient.NameValuePair;
- import org.apache.commons.httpclient.methods.GetMethod;
- import org.apache.commons.lang.math.DoubleRange;
- import org.apache.lucene.search.BooleanClause;
- import org.apache.lucene.search.BooleanQuery;
- import org.apache.lucene.search.TermRangeQuery;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.io.IOException;
- import java.util.List;
- import java.util.Locale;
- import static com.atlassian.jconnect.jira.customfields.GeoCalculator.*;
- /**
- * Query factory for '~' query for location.
- *
- */
- public class LocationLikeQueryFactory implements ClauseQueryFactory {
- private static final Logger logger = LoggerFactory.getLogger(LocationLikeQueryFactory.class);
- private final JiraAuthenticationContext authenticationContext;
- private final JqlOperandResolver jqlOperandResolver;
- private final DoubleConverter doubleConverter;
- private final String latFieldId;
- private final String lngFieldId;
- public LocationLikeQueryFactory(JqlOperandResolver jqlOperandResolver, String fieldId,
- DoubleConverter doubleConverter, JiraAuthenticationContext authenticationContext) {
- this.authenticationContext = authenticationContext;
- this.jqlOperandResolver = jqlOperandResolver;
- this.doubleConverter = doubleConverter;
- this.latFieldId = LocationIndexer.latFieldId(fieldId);
- this.lngFieldId= LocationIndexer.lngFieldId(fieldId);
- }
- public QueryFactoryResult getQuery(@NotNull QueryCreationContext queryCreationContext,
- @NotNull TerminalClause terminalClause) {
- try {
- return getQueryUnsafe(queryCreationContext, terminalClause);
- } catch (IllegalArgumentException e) {
- logger.warn("Could not parse the query: " + e.getMessage());
- return QueryFactoryResult.createFalseResult();
- }
- }
- private QueryFactoryResult getQueryUnsafe(QueryCreationContext queryCreationContext, TerminalClause terminalClause) {
- if (terminalClause.getOperator() == Operator.LIKE) {
- final LocationQuery query = LocationParser.parseLocationQuery(jqlOperandResolver.getSingleValue(
- queryCreationContext.getQueryUser(), terminalClause.getOperand(), terminalClause).getStringValue())
- .normalize();
- double minLat = getMinLat(query);
- double maxLat = getMaxLat(query);
- double minLng = getMinLng(query);
- double maxLng = getMaxLng(query);
- final BooleanQuery answer = new BooleanQuery();
- addLatQuery(minLat, maxLat, answer);
- addLngQuery(minLng, maxLng, answer);
- return new QueryFactoryResult(answer, false);
- } else if (terminalClause.getOperator() == Operator.IN) {
- final BooleanQuery answer = new BooleanQuery();
- final List<QueryLiteral> literals = jqlOperandResolver.getValues(queryCreationContext.getQueryUser(), terminalClause.getOperand(), terminalClause);
- for (QueryLiteral literal : literals) {
- JSONObject jsonObject = reverseGeo(literal.getStringValue());
- fillQuery(jsonObject, answer);
- }
- answer.setMinimumNumberShouldMatch(1);
- return new QueryFactoryResult(answer, false);
- } else {
- throw new IllegalArgumentException("Unsupported operator in clause " + terminalClause);
- }
- }
- private double getMaxLng(LocationQuery query) {
- double maxLng = query.lng + GeoCalculator.kmsToLongitude(query.lat, query.radius);
- if (!NORMALIZED_LNG_RANGE.containsDouble(maxLng)) {
- maxLng = NORMALIZED_LNG_RANGE.getMaximumDouble();
- }
- return maxLng;
- }
- private double getMinLng(LocationQuery query) {
- double minLng = query.lng - GeoCalculator.kmsToLongitude(query.lat, query.radius);
- if (!NORMALIZED_LNG_RANGE.containsDouble(minLng)) {
- minLng = NORMALIZED_LNG_RANGE.getMinimumDouble();
- }
- return minLng;
- }
- private double getMaxLat(LocationQuery query) {
- double maxLat = query.lat + GeoCalculator.kmsToLatitude(query.radius);
- if (!NORMALIZED_LAT_RANGE.containsDouble(maxLat)) {
- maxLat = NORMALIZED_LAT_RANGE.getMaximumDouble();
- }
- return maxLat;
- }
- private double getMinLat(LocationQuery query) {
- double minLat = query.lat - GeoCalculator.kmsToLatitude(query.radius);
- if (!NORMALIZED_LAT_RANGE.containsDouble(minLat)) {
- minLat = NORMALIZED_LAT_RANGE.getMinimumDouble();
- }
- return minLat;
- }
- private JSONObject reverseGeo(String address) {
- final HttpClient client = new HttpClient();
- final HttpMethod method = new GetMethod("http://maps.googleapis.com/maps/api/geocode/json");
- List<NameValuePair> query = Lists.newArrayList();
- query.add(new NameValuePair("sensor", "false"));
- query.add(new NameValuePair("address", address));
- query.add(new NameValuePair("region", getRegion()));
- method.setQueryString(query.toArray(new NameValuePair[query.size()]));
- logger.info("Executing method " + method);
- try {
- final int response = client.executeMethod(method);
- if (response == 200) {
- return new JSONObject(method.getResponseBodyAsString());
- }
- } catch (IOException e) {
- logger.error("Exception while executing request", e);
- } catch (JSONException e) {
- logger.error("Exception while parsing response", e);
- }
- return new JSONObject();
- }
- private void fillQuery(JSONObject response, BooleanQuery answer) {
- try {
- String status = (String) response.get("status");
- if (status.equals("OK")) {
- JSONArray results = response.getJSONArray("results");
- for (int i=0; i<results.length(); i++) {
- JSONObject location = results.getJSONObject(i);
- JSONObject geometry = location.getJSONObject("geometry");
- if (geometry.has("bounds")) {
- addSquareQuery(geometry.getJSONObject("bounds"), answer);
- } else {
- addSquareQuery(geometry.getJSONObject("viewport"), answer);
- }
- }
- } else {
- logger.warn("Error response from GMaps: " + status);
- }
- } catch (JSONException e) {
- logger.error("Error parsing GMaps response", e);
- }
- }
- private void addSquareQuery(JSONObject bounds, BooleanQuery mainQuery) throws JSONException {
- JSONObject sw = bounds.getJSONObject("southwest");
- double lat1 = (Double) sw.get("lat");
- double lng1 = (Double) sw.get("lng");
- JSONObject ne = bounds.getJSONObject("northeast");
- double lat2 = (Double) ne.get("lat");
- double lng2 = (Double) ne.get("lng");
- double minLat = normalizeLat(Math.min(lat1, lat2));
- double maxLat = normalizeLat(Math.max(lat1, lat2));
- double minLng = normalizeLng(Math.min(lng1, lng2));
- double maxLng = normalizeLng(Math.max(lng1, lng2));
- BooleanQuery subQuery = new BooleanQuery();
- addLatQuery(minLat, maxLat, subQuery);
- addLngQuery(minLng, maxLng, subQuery);
- mainQuery.add(subQuery, BooleanClause.Occur.SHOULD);
- }
- private String getRegion() {
- final Locale locale = authenticationContext.getLocale();
- String region = locale.getCountry();
- return !region.equals("gb") ? region : "uk";
- }
- // TODO use NumericRangeQuery in 4.4
- private void addLatQuery(double minLat, double maxLat, BooleanQuery answer) {
- if (!fullRange(minLat, maxLat, NORMALIZED_LAT_RANGE)) {
- answer.add(new TermRangeQuery(latFieldId, encode(minLat), encode(maxLat), true, true), BooleanClause.Occur.MUST);
- }
- }
- private void addLngQuery(double minLng, double maxLng, BooleanQuery answer) {
- if (!fullRange(minLng, maxLng, NORMALIZED_LNG_RANGE)) {
- answer.add(new TermRangeQuery(lngFieldId, encode(minLng), encode(maxLng), true, true), BooleanClause.Occur.MUST);
- }
- }
- private boolean fullRange(double min, double max, DoubleRange rangeToCompare) {
- return rangeToCompare.getMinimumDouble() == min && rangeToCompare.getMaximumDouble() == max;
- }
- private String encode(double val) {
- return doubleConverter.getStringForLucene(val);
- }
- }