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.jira.rest.client.api.IssueRestClient;
19  import com.atlassian.jira.rest.client.api.domain.Filter;
20  import com.atlassian.jira.rest.client.internal.json.FilterJsonParser;
21  import com.atlassian.jira.rest.client.internal.json.GenericJsonArrayParser;
22  import com.atlassian.jira.rest.client.internal.json.SearchResultJsonParser;
23  import com.atlassian.httpclient.api.HttpClient;
24  import com.atlassian.jira.rest.client.api.RestClientException;
25  import com.atlassian.jira.rest.client.api.SearchRestClient;
26  import com.atlassian.jira.rest.client.api.domain.SearchResult;
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.lang.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 }