Commits

Janusz Gorycki committed 9dd35f8

JRJC-98, JRJC-90, JRJC-71

  • Participants
  • Parent commits 7a6870b

Comments (0)

Files changed (13)

File src/main/java/com/atlassian/jira/rest/client/SearchRestClient.java

 	SearchResult searchJql(@Nullable String jql, int maxResults, int startAt, ProgressMonitor progressMonitor);
 
     /**
+     * Performs a JQL search and returns issues matching the query using default maxResults (as configured in JIRA - usually 50) and startAt=0
+     *
+     * @param jql a valid JQL query (will be properly encoded by JIRA client). Restricted JQL characters (like '/') must be properly escaped.
+     * @param maxResults maximum results (page/window size) for this search. The page will contain issues from
+     * <code>startAt div maxResults</code> (no remnant) and will include at most <code>maxResults</code> matching issues.
+     * @param startAt starting index (0-based) defining the page/window for the results. It will be aligned by the server to the beginning
+     * on the page (startAt = startAt div maxResults). For example for startAt=5 and maxResults=3 the results will include matching issues
+     * with index 3, 4 and 5. For startAt = 6 and maxResults=3 the issues returned are from position 6, 7 and 8.
+     * @param progressMonitor progress monitor
+     * @return issues matching given JQL query
+     * @throws RestClientException in case of problems (connectivity, malformed messages, invalid JQL query, etc.)
+     * @since 1.1 client 5.0 server
+     */
+    SearchResult searchJqlWithFullIssues(@Nullable String jql, int maxResults, int startAt, ProgressMonitor progressMonitor);
+
+    /**
      * @param pm
      * @return list of your favourite filters
-     * @since 1.2 client 5.0 server
+     * @since 1.1 client 5.0 server
      */
     Iterable<FavouriteFilter> getFavouriteFilters(NullProgressMonitor pm);
 }

File src/main/java/com/atlassian/jira/rest/client/domain/BasicIssue.java

 package com.atlassian.jira.rest.client.domain;
 
 import com.atlassian.jira.rest.client.AddressableEntity;
+import com.atlassian.jira.rest.client.IdentifiableEntity;
 import com.google.common.base.Objects;
 
+import javax.annotation.Nullable;
 import java.net.URI;
 
 /**
  *
  * @since v0.2
  */
-public class BasicIssue implements AddressableEntity {
+public class BasicIssue implements AddressableEntity, IdentifiableEntity<Long> {
 	private final URI self;
 
 	private final String key;
+    @Nullable
+    private final Long id;
 
-	public BasicIssue(URI self, String key) {
+	public BasicIssue(URI self, String key, @Nullable Long id) {
 		this.self = self;
 		this.key = key;
+        this.id = id;
 	}
 
-	/**
+    @Override
+    public Long getId() {
+        return id;
+    }
+
+    /**
 	 * @return URI of this issue
 	 */
 	@Override

File src/main/java/com/atlassian/jira/rest/client/domain/BasicStatus.java

 
     @Override
     public Long getId() {
-        return null;
+        return id;
     }
 }

File src/main/java/com/atlassian/jira/rest/client/domain/Issue.java

  */
 public class Issue extends BasicIssue implements ExpandableResource {
 
-	public Issue(String summary, URI self, String key, BasicProject project, BasicIssueType issueType, BasicStatus status,
+	public Issue(String summary, URI self, String key, @Nullable Long id, BasicProject project, BasicIssueType issueType, BasicStatus status,
 			String description, @Nullable BasicPriority priority, @Nullable BasicResolution resolution, Collection<Attachment> attachments,
 			@Nullable BasicUser reporter, @Nullable BasicUser assignee, DateTime creationDate, DateTime updateDate, DateTime dueDate,
 			Collection<Version> affectedVersions, Collection<Version> fixVersions, Collection<BasicComponent> components,
 			@Nullable Collection<IssueLink> issueLinks,
 			BasicVotes votes, Collection<Worklog> worklogs, BasicWatchers watchers, Iterable<String> expandos,
 			@Nullable Collection<Subtask> subtasks, @Nullable Collection<ChangelogGroup> changelog, Set<String> labels) {
-		super(self, key);
+		super(self, key, id);
 		this.summary = summary;
 		this.project = project;
 		this.status = status;

File src/main/java/com/atlassian/jira/rest/client/domain/SearchResult.java

 	private final int startIndex;
 	private final int maxResults;
 	private final int total;
-	private final Iterable<BasicIssue> issues;
+	private final Iterable<? extends BasicIssue> issues;
 
-	public SearchResult(int startIndex, int maxResults, int total, Iterable<BasicIssue> issues) {
+	public SearchResult(int startIndex, int maxResults, int total, Iterable<? extends BasicIssue> issues) {
 		this.startIndex = startIndex;
 		this.maxResults = maxResults;
 		this.total = total;
 		return total;
 	}
 
-	public Iterable<BasicIssue> getIssues() {
+	public Iterable<? extends BasicIssue> getIssues() {
 		return issues;
 	}
 

File src/main/java/com/atlassian/jira/rest/client/internal/jersey/JerseySearchRestClient.java

 
 package com.atlassian.jira.rest.client.internal.jersey;
 
-import com.atlassian.jira.rest.client.NullProgressMonitor;
-import com.atlassian.jira.rest.client.ProgressMonitor;
-import com.atlassian.jira.rest.client.RestClientException;
-import com.atlassian.jira.rest.client.SearchRestClient;
+import com.atlassian.jira.rest.client.*;
 import com.atlassian.jira.rest.client.domain.FavouriteFilter;
 import com.atlassian.jira.rest.client.domain.SearchResult;
 import com.atlassian.jira.rest.client.internal.json.FavouriteFilterJsonParser;
 import com.atlassian.jira.rest.client.internal.json.GenericJsonArrayParser;
 import com.atlassian.jira.rest.client.internal.json.SearchResultJsonParser;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
 import com.sun.jersey.client.apache.ApacheHttpClient;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 import javax.annotation.Nullable;
 import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
+import java.util.EnumSet;
 
 /**
  * Jersey-based implementation of SearchRestClient
  * @since v0.2
  */
 public class JerseySearchRestClient extends AbstractJerseyRestClient implements SearchRestClient{
-	private static final String START_AT_ATTRIBUTE = "startAt";
+    private static final EnumSet<IssueRestClient.Expandos> DEFAULT_EXPANDS = EnumSet.of(IssueRestClient.Expandos.NAMES, IssueRestClient.Expandos.SCHEMA);
+    private static final Function<IssueRestClient.Expandos, String> EXPANDO_TO_PARAM = new Function<IssueRestClient.Expandos, String>() {
+        @Override
+        public String apply(IssueRestClient.Expandos from) {
+            return from.name().toLowerCase();
+        }
+    };
+    private static final String EXPAND_ATTRIBUTE = "expand";
+    private static final String START_AT_ATTRIBUTE = "startAt";
 	private static final String MAX_RESULTS_ATTRIBUTE = "maxResults";
 	private static final int MAX_JQL_LENGTH_FOR_HTTP_GET = 500;
 	private static final String JQL_ATTRIBUTE = "jql";
-	private final SearchResultJsonParser searchResultJsonParser = new SearchResultJsonParser();
+	private final SearchResultJsonParser keyOnlySearchResultJsonParser = new SearchResultJsonParser(false);
+    private final SearchResultJsonParser fullSearchResultJsonParser = new SearchResultJsonParser(true);
     private final GenericJsonArrayParser<FavouriteFilter> favouriteFiltersJsonParser = GenericJsonArrayParser.create(new FavouriteFilterJsonParser());
 
 	private static final String SEARCH_URI_PREFIX = "search";
 
 	@Override
 	public SearchResult searchJql(@Nullable String jql, ProgressMonitor progressMonitor) {
-		if (jql == null) {
-			jql = "";
-		}
-		if (jql.length() > MAX_JQL_LENGTH_FOR_HTTP_GET) {
-			final JSONObject postEntity = new JSONObject();
-			try {
-				postEntity.put(JQL_ATTRIBUTE, jql);
-			} catch (JSONException e) {
-				throw new RestClientException(e);
-			}
-			return postAndParse(searchUri, postEntity, searchResultJsonParser, progressMonitor);
-		} else {
-			final URI uri = UriBuilder.fromUri(searchUri).queryParam(JQL_ATTRIBUTE, jql).build();
-			return getAndParse(uri, searchResultJsonParser, progressMonitor);
-		}
+        return searchJqlImpl(jql, null, null, progressMonitor, keyOnlySearchResultJsonParser);
 	}
 
 	@Override
 	public SearchResult searchJql(@Nullable String jql, int maxResults, int startAt, ProgressMonitor progressMonitor) {
-		if (jql == null) {
-			jql = "";
-		}
-		if (jql.length() > MAX_JQL_LENGTH_FOR_HTTP_GET) {
-			final JSONObject postEntity = new JSONObject();
-			try {
-				postEntity.put(JQL_ATTRIBUTE, jql);
-				postEntity.put(START_AT_ATTRIBUTE, startAt);
-				postEntity.put(MAX_RESULTS_ATTRIBUTE, maxResults);
-			} catch (JSONException e) {
-				throw new RestClientException(e);
-			}
-			return postAndParse(searchUri, postEntity, searchResultJsonParser, progressMonitor);
-		} else {
-			final URI uri = UriBuilder.fromUri(searchUri).queryParam(JQL_ATTRIBUTE, jql).queryParam(MAX_RESULTS_ATTRIBUTE, maxResults)
-					.queryParam(START_AT_ATTRIBUTE, startAt).build();
-			return getAndParse(uri, searchResultJsonParser, progressMonitor);
-		}
+		return searchJqlImpl(jql, maxResults, startAt, progressMonitor, keyOnlySearchResultJsonParser);
 	}
 
     @Override
+    public SearchResult searchJqlWithFullIssues(@Nullable String jql, int maxResults, int startAt, ProgressMonitor progressMonitor) {
+        return searchJqlImpl(jql, maxResults, startAt, progressMonitor, fullSearchResultJsonParser);
+    }
+
+    private SearchResult searchJqlImpl(@Nullable String jql, Integer maxResults, Integer startAt, ProgressMonitor progressMonitor, SearchResultJsonParser parser) {
+        if (jql == null) {
+            jql = "";
+        }
+
+        if (jql.length() > MAX_JQL_LENGTH_FOR_HTTP_GET) {
+            UriBuilder uriBuilder = UriBuilder.fromUri(searchUri);
+            final JSONObject postEntity = new JSONObject();
+            try {
+                postEntity.put(JQL_ATTRIBUTE, jql);
+                if (maxResults != null && startAt != null) {
+                    postEntity.put(START_AT_ATTRIBUTE, startAt);
+                    postEntity.put(MAX_RESULTS_ATTRIBUTE, maxResults);
+                }
+                uriBuilder = uriBuilder.queryParam(EXPAND_ATTRIBUTE, Joiner.on(',').join(Iterables.transform(DEFAULT_EXPANDS, EXPANDO_TO_PARAM)));
+            } catch (JSONException e) {
+                throw new RestClientException(e);
+            }
+            return postAndParse(uriBuilder.build(), postEntity, parser, progressMonitor);
+        } else {
+            UriBuilder uriBuilder = UriBuilder.fromUri(searchUri).queryParam(JQL_ATTRIBUTE, jql);
+            if (maxResults != null && startAt != null) {
+                uriBuilder = uriBuilder.queryParam(MAX_RESULTS_ATTRIBUTE, maxResults).queryParam(START_AT_ATTRIBUTE, startAt);
+            }
+            URI uri = uriBuilder.queryParam(EXPAND_ATTRIBUTE, Joiner.on(',').join(Iterables.transform(DEFAULT_EXPANDS, EXPANDO_TO_PARAM))).build();
+            return getAndParse(uri, parser, progressMonitor);
+        }
+    }
+
+        @Override
     public Iterable<FavouriteFilter> getFavouriteFilters(NullProgressMonitor progressMonitor) {
         final URI uri = UriBuilder.fromUri(baseUri).path("filter/favourite").build();
         return getAndParse(uri, favouriteFiltersJsonParser, progressMonitor);

File src/main/java/com/atlassian/jira/rest/client/internal/json/BasicIssueJsonParser.java

 	public BasicIssue parse(JSONObject json) throws JSONException {
 		final URI selfUri = JsonParseUtil.getSelfUri(json);
 		final String key = json.getString("key");
-		return new BasicIssue(selfUri, key);
+        final Long id = JsonParseUtil.getOptionalLong(json, "id");
+		return new BasicIssue(selfUri, key, id);
 	}
 }

File src/main/java/com/atlassian/jira/rest/client/internal/json/BasicPriorityJsonParser.java

 package com.atlassian.jira.rest.client.internal.json;
 
 import com.atlassian.jira.rest.client.domain.BasicPriority;
+import com.atlassian.jira.rest.client.domain.Priority;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 
 		final String name = json.getString("name");
 		final Long id = JsonParseUtil.getOptionalLong(json, "id");
 		final URI selfUri = JsonParseUtil.getSelfUri(json);
+        final String iconUrl = JsonParseUtil.getOptionalString(json, "iconUrl");
+        if (iconUrl != null) {
+            return new Priority(selfUri, id, name, null, null, JsonParseUtil.parseURI(iconUrl));
+        }
 		return new BasicPriority(selfUri, id, name);
 	}
 }

File src/main/java/com/atlassian/jira/rest/client/internal/json/BasicStatusJsonParser.java

 package com.atlassian.jira.rest.client.internal.json;
 
 import com.atlassian.jira.rest.client.domain.BasicStatus;
+import com.atlassian.jira.rest.client.domain.Status;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 
 		final URI self = JsonParseUtil.getSelfUri(json);
 		final String name = json.getString("name");
         final Long id = JsonParseUtil.getOptionalLong(json, "id");
+        final String iconUrl = JsonParseUtil.getOptionalString(json, "iconUrl");
+        if (iconUrl != null) {
+            return new Status(self, name, null, JsonParseUtil.parseURI(iconUrl), id);
+        }
 		return new BasicStatus(self, name, id);
 	}
 }

File src/main/java/com/atlassian/jira/rest/client/internal/json/IssueJsonParser.java

 
 	private static final String FIELDS = "fields";
 	private static final String VALUE_ATTR = "value";
-
-	static Iterable<String> parseExpandos(JSONObject json) throws JSONException {
+    private final boolean jira50ByDefault;
+    private final JSONObject namesFromSearchQuery;
+    private final JSONObject schemaFromSearchQuery;
+
+    public IssueJsonParser() {
+        jira50ByDefault = false;
+        this.namesFromSearchQuery = null;
+        this.schemaFromSearchQuery = null;
+    }
+
+    public IssueJsonParser(boolean isJira50, JSONObject names, JSONObject schema) {
+        jira50ByDefault = isJira50;
+        this.namesFromSearchQuery = names;
+        this.schemaFromSearchQuery = schema;
+    }
+
+    static Iterable<String> parseExpandos(JSONObject json) throws JSONException {
 		final String expando = json.getString("expand");
 		return Splitter.on(',').split(expando);
 	}
 	@Override
 	public Issue parse(JSONObject s) throws JSONException {
 		final Iterable<String> expandos = parseExpandos(s);
-		final boolean isJira5x0OrNewer = Iterables.contains(expandos, SCHEMA_SECTION);
+		final boolean isJira5x0OrNewer = Iterables.contains(expandos, SCHEMA_SECTION) || jira50ByDefault;
 		final boolean shouldUseNestedValueAttribute = !isJira5x0OrNewer;
 		final Collection<Comment> comments;
 		if (isJira5x0OrNewer) {
-			final JSONObject commentsJson = s.getJSONObject(FIELDS).getJSONObject(COMMENT_FIELD.id);
-			comments = parseArray(commentsJson, new JsonWeakParserForJsonObject<Comment>(commentJsonParser), "comments");
-
+            final JSONObject commentsJson = JsonParseUtil.getOptionalJsonObject(s.getJSONObject(FIELDS), COMMENT_FIELD.id);
+            if (commentsJson != null) {
+                comments = parseArray(commentsJson, new JsonWeakParserForJsonObject<Comment>(commentJsonParser), "comments");
+            } else {
+                comments = Lists.newArrayList();
+            }
 		} else {
 			final Collection<Comment> commentsTmp = parseOptionalArray(
 					shouldUseNestedValueAttribute, s, new JsonWeakParserForJsonObject<Comment>(commentJsonParser), FIELDS, COMMENT_FIELD.id);
 				jsonWeakParserForString, FIELDS, LABELS_FIELD.id));
 
 		final Collection<ChangelogGroup> changelog = parseOptionalArray(false, s, new JsonWeakParserForJsonObject<ChangelogGroup>(changelogJsonParser), "changelog", "histories");
-		return new Issue(summary, selfUri, s.getString("key"), project, issueType, status,
+		return new Issue(summary, selfUri, s.getString("key"), JsonParseUtil.getOptionalLong(s, "id"), project, issueType, status,
 				description, priority, resolution, attachments, reporter, assignee, creationDate, updateDate,
 				dueDate, affectedVersions, fixVersions, components, timeTracking, fields, comments,
 				transitionsUri, issueLinks,
 	}
 
 	private Collection<Field> parseFieldsJira5x0(JSONObject issueJson) throws JSONException {
-		final JSONObject names = issueJson.optJSONObject(NAMES_SECTION);
-		final Map<String, String> namesMap = parseNames(names);
-		final JSONObject types = issueJson.optJSONObject(SCHEMA_SECTION);
-		final Map<String, String> typesMap = parseSchema(types);
+		final JSONObject names = namesFromSearchQuery != null ? namesFromSearchQuery : JsonParseUtil.getOptionalJsonObject(issueJson, NAMES_SECTION);
+        final JSONObject types = schemaFromSearchQuery != null ? schemaFromSearchQuery : JsonParseUtil.getOptionalJsonObject(issueJson, SCHEMA_SECTION);
+        final JSONObject json = issueJson.getJSONObject(FIELDS);
+        final ArrayList<Field> res = new ArrayList<Field>(json.length());
+        if (names == null || types == null) {
+            return res;
+        }
+        final Map<String, String> namesMap = parseNames(names);
+        final Map<String, String> typesMap = parseSchema(types);
 
-		final JSONObject json = issueJson.getJSONObject(FIELDS);
-		final ArrayList<Field> res = new ArrayList<Field>(json.length());
 		@SuppressWarnings("unchecked")
 		final Iterator<String> iterator = json.keys();
 		while (iterator.hasNext()) {

File src/main/java/com/atlassian/jira/rest/client/internal/json/SearchResultJsonParser.java

 import java.util.Collection;
 
 public class SearchResultJsonParser implements JsonObjectParser<SearchResult> {
-	private final BasicIssueJsonParser basicIssueJsonParser = new BasicIssueJsonParser();
+    private final boolean full;
 
-	@Override
+    public SearchResultJsonParser(boolean full) {
+        this.full = full;
+    }
+
+    @Override
 	public SearchResult parse(JSONObject json) throws JSONException {
 		final int startAt = json.getInt("startAt");
 		final int maxResults = json.getInt("maxResults");
 		final int total = json.getInt("total");
-		final Collection<BasicIssue> issues = JsonParseUtil.parseJsonArray(json.getJSONArray("issues"), basicIssueJsonParser);
+        final JSONObject names = JsonParseUtil.getOptionalJsonObject(json, "names");
+        final JSONObject schema = JsonParseUtil.getOptionalJsonObject(json, "schema");
+		final Collection<? extends BasicIssue> issues = JsonParseUtil.parseJsonArray(
+                json.getJSONArray("issues"), full ? new IssueJsonParser(true, names, schema) : new BasicIssueJsonParser());
 		return new SearchResult(startAt, maxResults, total, issues);
 	}
 }

File src/test/java/com/atlassian/jira/rest/client/internal/json/BasicIssueJsonParserTest.java

 	public void testParse() throws Exception {
 		BasicIssueJsonParser parser = new BasicIssueJsonParser();
 		final BasicIssue basicIssue = parser.parse(ResourceUtil.getJsonObjectFromResource("/json/search/issues1.json").getJSONArray("issues").getJSONObject(0));
-		assertEquals(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-7"), "TST-7"), basicIssue);
+		assertEquals(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-7"), "TST-7", null), basicIssue);
 	}
 }

File src/test/java/com/atlassian/jira/rest/client/internal/json/SearchResultJsonParserTest.java

 	@Rule
 	public final ExpectedException exception = ExpectedException.none();
 
-	final SearchResultJsonParser parser = new SearchResultJsonParser();
+	final SearchResultJsonParser parser = new SearchResultJsonParser(false);
 
 	@Test
 	public void testParse() throws Exception {
 		final SearchResult searchResult = parser.parse(ResourceUtil.getJsonObjectFromResource("/json/search/issues1.json"));
-		final ArrayList<BasicIssue> issues = Lists.newArrayList(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-7"), "TST-7"));
+		final ArrayList<BasicIssue> issues = Lists.newArrayList(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-7"), "TST-7", null));
 
 		assertEquals(new SearchResult(0, 50, 1, issues), searchResult);
 	}
 		assertEquals(8, searchResult.getMaxResults());
 		assertEquals(0, searchResult.getStartIndex());
 		assertEquals(8, Iterables.size(searchResult.getIssues()));
-		assertEquals(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/RST-1"), "RST-1"), Iterables.getLast(searchResult.getIssues()));
+		assertEquals(new BasicIssue(toUri("http://localhost:8090/jira/rest/api/latest/issue/RST-1"), "RST-1", null), Iterables.getLast(searchResult.getIssues()));
 	}
 
 	@Test