PageRenderTime 53ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/atlassian/jconnect/jira/customfields/LocationLikeQueryFactory.java

https://bitbucket.org/atlassian/jiraconnect-jiraplugin/
Java | 215 lines | 187 code | 23 blank | 5 comment | 23 complexity | 64adf3819031ee52bdb855ad8a8fd3cc MD5 | raw file
  1. package com.atlassian.jconnect.jira.customfields;
  2. import com.atlassian.jira.issue.customfields.converters.DoubleConverter;
  3. import com.atlassian.jira.jql.operand.JqlOperandResolver;
  4. import com.atlassian.jira.jql.operand.QueryLiteral;
  5. import com.atlassian.jira.jql.query.ClauseQueryFactory;
  6. import com.atlassian.jira.jql.query.QueryCreationContext;
  7. import com.atlassian.jira.jql.query.QueryFactoryResult;
  8. import com.atlassian.jira.security.JiraAuthenticationContext;
  9. import com.atlassian.jira.util.NotNull;
  10. import com.atlassian.jira.util.json.JSONArray;
  11. import com.atlassian.jira.util.json.JSONException;
  12. import com.atlassian.jira.util.json.JSONObject;
  13. import com.atlassian.query.clause.TerminalClause;
  14. import com.atlassian.query.operator.Operator;
  15. import com.google.common.collect.Lists;
  16. import org.apache.commons.httpclient.HttpClient;
  17. import org.apache.commons.httpclient.HttpMethod;
  18. import org.apache.commons.httpclient.NameValuePair;
  19. import org.apache.commons.httpclient.methods.GetMethod;
  20. import org.apache.commons.lang.math.DoubleRange;
  21. import org.apache.lucene.search.BooleanClause;
  22. import org.apache.lucene.search.BooleanQuery;
  23. import org.apache.lucene.search.TermRangeQuery;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import java.io.IOException;
  27. import java.util.List;
  28. import java.util.Locale;
  29. import static com.atlassian.jconnect.jira.customfields.GeoCalculator.*;
  30. /**
  31. * Query factory for '~' query for location.
  32. *
  33. */
  34. public class LocationLikeQueryFactory implements ClauseQueryFactory {
  35. private static final Logger logger = LoggerFactory.getLogger(LocationLikeQueryFactory.class);
  36. private final JiraAuthenticationContext authenticationContext;
  37. private final JqlOperandResolver jqlOperandResolver;
  38. private final DoubleConverter doubleConverter;
  39. private final String latFieldId;
  40. private final String lngFieldId;
  41. public LocationLikeQueryFactory(JqlOperandResolver jqlOperandResolver, String fieldId,
  42. DoubleConverter doubleConverter, JiraAuthenticationContext authenticationContext) {
  43. this.authenticationContext = authenticationContext;
  44. this.jqlOperandResolver = jqlOperandResolver;
  45. this.doubleConverter = doubleConverter;
  46. this.latFieldId = LocationIndexer.latFieldId(fieldId);
  47. this.lngFieldId= LocationIndexer.lngFieldId(fieldId);
  48. }
  49. public QueryFactoryResult getQuery(@NotNull QueryCreationContext queryCreationContext,
  50. @NotNull TerminalClause terminalClause) {
  51. try {
  52. return getQueryUnsafe(queryCreationContext, terminalClause);
  53. } catch (IllegalArgumentException e) {
  54. logger.warn("Could not parse the query: " + e.getMessage());
  55. return QueryFactoryResult.createFalseResult();
  56. }
  57. }
  58. private QueryFactoryResult getQueryUnsafe(QueryCreationContext queryCreationContext, TerminalClause terminalClause) {
  59. if (terminalClause.getOperator() == Operator.LIKE) {
  60. final LocationQuery query = LocationParser.parseLocationQuery(jqlOperandResolver.getSingleValue(
  61. queryCreationContext.getQueryUser(), terminalClause.getOperand(), terminalClause).getStringValue())
  62. .normalize();
  63. double minLat = getMinLat(query);
  64. double maxLat = getMaxLat(query);
  65. double minLng = getMinLng(query);
  66. double maxLng = getMaxLng(query);
  67. final BooleanQuery answer = new BooleanQuery();
  68. addLatQuery(minLat, maxLat, answer);
  69. addLngQuery(minLng, maxLng, answer);
  70. return new QueryFactoryResult(answer, false);
  71. } else if (terminalClause.getOperator() == Operator.IN) {
  72. final BooleanQuery answer = new BooleanQuery();
  73. final List<QueryLiteral> literals = jqlOperandResolver.getValues(queryCreationContext.getQueryUser(), terminalClause.getOperand(), terminalClause);
  74. for (QueryLiteral literal : literals) {
  75. JSONObject jsonObject = reverseGeo(literal.getStringValue());
  76. fillQuery(jsonObject, answer);
  77. }
  78. answer.setMinimumNumberShouldMatch(1);
  79. return new QueryFactoryResult(answer, false);
  80. } else {
  81. throw new IllegalArgumentException("Unsupported operator in clause " + terminalClause);
  82. }
  83. }
  84. private double getMaxLng(LocationQuery query) {
  85. double maxLng = query.lng + GeoCalculator.kmsToLongitude(query.lat, query.radius);
  86. if (!NORMALIZED_LNG_RANGE.containsDouble(maxLng)) {
  87. maxLng = NORMALIZED_LNG_RANGE.getMaximumDouble();
  88. }
  89. return maxLng;
  90. }
  91. private double getMinLng(LocationQuery query) {
  92. double minLng = query.lng - GeoCalculator.kmsToLongitude(query.lat, query.radius);
  93. if (!NORMALIZED_LNG_RANGE.containsDouble(minLng)) {
  94. minLng = NORMALIZED_LNG_RANGE.getMinimumDouble();
  95. }
  96. return minLng;
  97. }
  98. private double getMaxLat(LocationQuery query) {
  99. double maxLat = query.lat + GeoCalculator.kmsToLatitude(query.radius);
  100. if (!NORMALIZED_LAT_RANGE.containsDouble(maxLat)) {
  101. maxLat = NORMALIZED_LAT_RANGE.getMaximumDouble();
  102. }
  103. return maxLat;
  104. }
  105. private double getMinLat(LocationQuery query) {
  106. double minLat = query.lat - GeoCalculator.kmsToLatitude(query.radius);
  107. if (!NORMALIZED_LAT_RANGE.containsDouble(minLat)) {
  108. minLat = NORMALIZED_LAT_RANGE.getMinimumDouble();
  109. }
  110. return minLat;
  111. }
  112. private JSONObject reverseGeo(String address) {
  113. final HttpClient client = new HttpClient();
  114. final HttpMethod method = new GetMethod("http://maps.googleapis.com/maps/api/geocode/json");
  115. List<NameValuePair> query = Lists.newArrayList();
  116. query.add(new NameValuePair("sensor", "false"));
  117. query.add(new NameValuePair("address", address));
  118. query.add(new NameValuePair("region", getRegion()));
  119. method.setQueryString(query.toArray(new NameValuePair[query.size()]));
  120. logger.info("Executing method " + method);
  121. try {
  122. final int response = client.executeMethod(method);
  123. if (response == 200) {
  124. return new JSONObject(method.getResponseBodyAsString());
  125. }
  126. } catch (IOException e) {
  127. logger.error("Exception while executing request", e);
  128. } catch (JSONException e) {
  129. logger.error("Exception while parsing response", e);
  130. }
  131. return new JSONObject();
  132. }
  133. private void fillQuery(JSONObject response, BooleanQuery answer) {
  134. try {
  135. String status = (String) response.get("status");
  136. if (status.equals("OK")) {
  137. JSONArray results = response.getJSONArray("results");
  138. for (int i=0; i<results.length(); i++) {
  139. JSONObject location = results.getJSONObject(i);
  140. JSONObject geometry = location.getJSONObject("geometry");
  141. if (geometry.has("bounds")) {
  142. addSquareQuery(geometry.getJSONObject("bounds"), answer);
  143. } else {
  144. addSquareQuery(geometry.getJSONObject("viewport"), answer);
  145. }
  146. }
  147. } else {
  148. logger.warn("Error response from GMaps: " + status);
  149. }
  150. } catch (JSONException e) {
  151. logger.error("Error parsing GMaps response", e);
  152. }
  153. }
  154. private void addSquareQuery(JSONObject bounds, BooleanQuery mainQuery) throws JSONException {
  155. JSONObject sw = bounds.getJSONObject("southwest");
  156. double lat1 = (Double) sw.get("lat");
  157. double lng1 = (Double) sw.get("lng");
  158. JSONObject ne = bounds.getJSONObject("northeast");
  159. double lat2 = (Double) ne.get("lat");
  160. double lng2 = (Double) ne.get("lng");
  161. double minLat = normalizeLat(Math.min(lat1, lat2));
  162. double maxLat = normalizeLat(Math.max(lat1, lat2));
  163. double minLng = normalizeLng(Math.min(lng1, lng2));
  164. double maxLng = normalizeLng(Math.max(lng1, lng2));
  165. BooleanQuery subQuery = new BooleanQuery();
  166. addLatQuery(minLat, maxLat, subQuery);
  167. addLngQuery(minLng, maxLng, subQuery);
  168. mainQuery.add(subQuery, BooleanClause.Occur.SHOULD);
  169. }
  170. private String getRegion() {
  171. final Locale locale = authenticationContext.getLocale();
  172. String region = locale.getCountry();
  173. return !region.equals("gb") ? region : "uk";
  174. }
  175. // TODO use NumericRangeQuery in 4.4
  176. private void addLatQuery(double minLat, double maxLat, BooleanQuery answer) {
  177. if (!fullRange(minLat, maxLat, NORMALIZED_LAT_RANGE)) {
  178. answer.add(new TermRangeQuery(latFieldId, encode(minLat), encode(maxLat), true, true), BooleanClause.Occur.MUST);
  179. }
  180. }
  181. private void addLngQuery(double minLng, double maxLng, BooleanQuery answer) {
  182. if (!fullRange(minLng, maxLng, NORMALIZED_LNG_RANGE)) {
  183. answer.add(new TermRangeQuery(lngFieldId, encode(minLng), encode(maxLng), true, true), BooleanClause.Occur.MUST);
  184. }
  185. }
  186. private boolean fullRange(double min, double max, DoubleRange rangeToCompare) {
  187. return rangeToCompare.getMinimumDouble() == min && rangeToCompare.getMaximumDouble() == max;
  188. }
  189. private String encode(double val) {
  190. return doubleConverter.getStringForLucene(val);
  191. }
  192. }