View Javadoc

1   /*
2    * Copyright (C) 2012 Atlassian
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.atlassian.jira.rest.client.internal.async;
17  
18  import com.atlassian.httpclient.api.HttpClient;
19  import com.atlassian.jira.rest.client.api.IssueRestClient;
20  import com.atlassian.jira.rest.client.api.RestClientException;
21  import com.atlassian.jira.rest.client.api.SearchRestClient;
22  import com.atlassian.jira.rest.client.api.domain.Filter;
23  import com.atlassian.jira.rest.client.api.domain.SearchResult;
24  import com.atlassian.jira.rest.client.internal.json.FilterJsonParser;
25  import com.atlassian.jira.rest.client.internal.json.GenericJsonArrayParser;
26  import com.atlassian.jira.rest.client.internal.json.SearchResultJsonParser;
27  import com.atlassian.util.concurrent.Promise;
28  import com.google.common.base.Function;
29  import com.google.common.base.Joiner;
30  import com.google.common.collect.ImmutableList;
31  import com.google.common.collect.Iterables;
32  import org.apache.commons.lang3.StringUtils;
33  import org.codehaus.jettison.json.JSONException;
34  import org.codehaus.jettison.json.JSONObject;
35  
36  import javax.annotation.Nullable;
37  import javax.ws.rs.core.UriBuilder;
38  import java.net.URI;
39  import java.util.Set;
40  
41  import static com.atlassian.jira.rest.client.api.IssueRestClient.Expandos.NAMES;
42  import static com.atlassian.jira.rest.client.api.IssueRestClient.Expandos.SCHEMA;
43  
44  /**
45   * Asynchronous implementation of SearchRestClient.
46   *
47   * @since v2.0
48   */
49  public class AsynchronousSearchRestClient extends AbstractAsynchronousRestClient implements SearchRestClient {
50  
51      private static final Function<IssueRestClient.Expandos, String> EXPANDO_TO_PARAM = new Function<IssueRestClient.Expandos, String>() {
52          @Override
53          public String apply(IssueRestClient.Expandos from) {
54              return from.name().toLowerCase();
55          }
56      };
57  
58      private static final String START_AT_ATTRIBUTE = "startAt";
59      private static final String MAX_RESULTS_ATTRIBUTE = "maxResults";
60      private static final int MAX_JQL_LENGTH_FOR_HTTP_GET = 500;
61      private static final String JQL_ATTRIBUTE = "jql";
62      private static final String FILTER_FAVOURITE_PATH = "filter/favourite";
63      private static final String FILTER_PATH_FORMAT = "filter/%s";
64      private static final String SEARCH_URI_PREFIX = "search";
65      private static final String EXPAND_ATTRIBUTE = "expand";
66      private static final String FIELDS_ATTRIBUTE = "fields";
67  
68      private final SearchResultJsonParser searchResultJsonParser = new SearchResultJsonParser();
69      private final FilterJsonParser filterJsonParser = new FilterJsonParser();
70      private final GenericJsonArrayParser<Filter> filtersParser = GenericJsonArrayParser.create(new FilterJsonParser());
71  
72      private final URI searchUri;
73      private final URI favouriteUri;
74      private final URI baseUri;
75  
76      public AsynchronousSearchRestClient(final URI baseUri, final HttpClient asyncHttpClient) {
77          super(asyncHttpClient);
78          this.baseUri = baseUri;
79          this.searchUri = UriBuilder.fromUri(baseUri).path(SEARCH_URI_PREFIX).build();
80          this.favouriteUri = UriBuilder.fromUri(baseUri).path(FILTER_FAVOURITE_PATH).build();
81      }
82  
83      @Override
84      public Promise<SearchResult> searchJql(@Nullable String jql) {
85          return searchJql(jql, null, null, null);
86      }
87  
88      public Promise<SearchResult> searchJql(@Nullable String jql, @Nullable Integer maxResults, @Nullable Integer startAt, @Nullable Set<String> fields) {
89          final Iterable<String> expandosValues = Iterables.transform(ImmutableList.of(SCHEMA, NAMES), EXPANDO_TO_PARAM);
90          final String notNullJql = StringUtils.defaultString(jql);
91          if (notNullJql.length() > MAX_JQL_LENGTH_FOR_HTTP_GET) {
92              return searchJqlImplPost(maxResults, startAt, expandosValues, notNullJql, fields);
93          } else {
94              return searchJqlImplGet(maxResults, startAt, expandosValues, notNullJql, fields);
95          }
96      }
97  
98      private Promise<SearchResult> searchJqlImplGet(@Nullable Integer maxResults, @Nullable Integer startAt, Iterable<String> expandosValues, String jql, @Nullable Set<String> fields) {
99          final UriBuilder uriBuilder = UriBuilder.fromUri(searchUri)
100                 .queryParam(JQL_ATTRIBUTE, jql)
101                 .queryParam(EXPAND_ATTRIBUTE, Joiner.on(",").join(expandosValues));
102 
103         if (fields != null) {
104             uriBuilder.queryParam(FIELDS_ATTRIBUTE, Joiner.on(",").join(fields));
105         }
106         addOptionalQueryParam(uriBuilder, MAX_RESULTS_ATTRIBUTE, maxResults);
107         addOptionalQueryParam(uriBuilder, START_AT_ATTRIBUTE, startAt);
108 
109         return getAndParse(uriBuilder.build(), searchResultJsonParser);
110     }
111 
112     private void addOptionalQueryParam(final UriBuilder uriBuilder, final String key, final Object... values) {
113         if (values != null && values.length > 0 && values[0] != null) {
114             uriBuilder.queryParam(key, values);
115         }
116     }
117 
118     private Promise<SearchResult> searchJqlImplPost(@Nullable Integer maxResults, @Nullable Integer startAt, Iterable<String> expandosValues, String jql, @Nullable Set<String> fields) {
119         final JSONObject postEntity = new JSONObject();
120 
121         try {
122             postEntity.put(JQL_ATTRIBUTE, jql)
123                     .put(EXPAND_ATTRIBUTE, ImmutableList.copyOf(expandosValues))
124                     .putOpt(START_AT_ATTRIBUTE, startAt)
125                     .putOpt(MAX_RESULTS_ATTRIBUTE, maxResults);
126 
127             if (fields != null) {
128                 postEntity.put(FIELDS_ATTRIBUTE, fields); // putOpt doesn't work with collections
129             }
130         } catch (JSONException e) {
131             throw new RestClientException(e);
132         }
133         return postAndParse(searchUri, postEntity, searchResultJsonParser);
134     }
135 
136     @Override
137     public Promise<Iterable<Filter>> getFavouriteFilters() {
138         return getAndParse(favouriteUri, filtersParser);
139     }
140 
141     @Override
142     public Promise<Filter> getFilter(URI filterUri) {
143         return getAndParse(filterUri, filterJsonParser);
144     }
145 
146     @Override
147     public Promise<Filter> getFilter(long id) {
148         return getFilter(UriBuilder.fromUri(baseUri).path(String.format(FILTER_PATH_FORMAT, id)).build());
149     }
150 }