9
9
10
10
import com .atlassian .httpclient .apache .httpcomponents .DefaultHttpClientFactory ;
11
11
import com .atlassian .httpclient .api .HttpClient ;
12
+ import com .atlassian .httpclient .api .Response ;
12
13
import com .atlassian .httpclient .api .factory .HttpClientOptions ;
13
14
import com .atlassian .jira .rest .client .api .AuthenticationHandler ;
14
- import com .atlassian .jira .rest .client .api .GetCreateIssueMetadataOptionsBuilder ;
15
15
import com .atlassian .jira .rest .client .api .JiraRestClient ;
16
- import com .atlassian .jira .rest .client .api .domain .BasicComponent ;
17
- import com .atlassian .jira .rest .client .api .domain .BasicIssue ;
18
- import com .atlassian .jira .rest .client .api .domain .CimIssueType ;
19
- import com .atlassian .jira .rest .client .api .domain .CimProject ;
20
- import com .atlassian .jira .rest .client .api .domain .Issue ;
21
- import com .atlassian .jira .rest .client .api .domain .IssueType ;
22
- import com .atlassian .jira .rest .client .api .domain .Project ;
23
- import com .atlassian .jira .rest .client .api .domain .User ;
16
+ import com .atlassian .jira .rest .client .api .domain .*;
24
17
import com .atlassian .jira .rest .client .auth .BasicHttpAuthenticationHandler ;
25
18
import com .atlassian .jira .rest .client .internal .async .AsynchronousJiraRestClient ;
26
19
import com .atlassian .jira .rest .client .internal .async .AtlassianHttpClientDecorator ;
27
20
import com .atlassian .jira .rest .client .internal .async .DisposableHttpClient ;
21
+ import com .atlassian .jira .rest .client .internal .json .CimFieldsInfoJsonParser ;
22
+ import com .atlassian .jira .rest .client .internal .json .GenericJsonArrayParser ;
23
+ import com .atlassian .jira .rest .client .internal .json .IssueTypeJsonParser ;
24
+ import com .atlassian .jira .rest .client .internal .json .PageJsonParser ;
28
25
import com .devexperts .switchboard .api .IntegrationFeatures ;
26
+ import org .codehaus .jettison .json .JSONArray ;
27
+ import org .codehaus .jettison .json .JSONException ;
28
+ import org .codehaus .jettison .json .JSONObject ;
29
29
30
30
import java .net .URI ;
31
- import java .util .ArrayList ;
32
- import java .util .Collections ;
33
- import java .util .HashMap ;
34
- import java .util .List ;
35
- import java .util .Map ;
36
- import java .util .Objects ;
37
- import java .util .Set ;
31
+ import java .util .*;
38
32
import java .util .concurrent .TimeUnit ;
33
+ import java .util .concurrent .atomic .AtomicInteger ;
39
34
import java .util .stream .Collectors ;
40
35
import java .util .stream .StreamSupport ;
41
36
@@ -50,16 +45,21 @@ public class JiraIntegrationFeatures implements IntegrationFeatures {
50
45
private final Map <String , Project > projectCache = new HashMap <>();
51
46
private final Map <String , CimProject > cimProjectCache = new HashMap <>();
52
47
private final Map <String , Map <String , CimIssueType >> projectIssueTypeCache = new HashMap <>();
48
+ private final URI serverUri ;
53
49
private final Map <String , User > usersCache = new HashMap <>();
54
50
55
51
private final int socketTimeoutSeconds ;
56
52
private final int searchQueryBatch ;
57
53
54
+ private final DisposableHttpClient httpClient ;
55
+
58
56
JiraIntegrationFeatures (URI serverUri , String username , String password , int socketTimeoutSeconds , int searchQueryBatch ) {
57
+ this .serverUri = serverUri ;
59
58
this .socketTimeoutSeconds = socketTimeoutSeconds ;
60
59
this .searchQueryBatch = searchQueryBatch ;
61
60
this .authenticationHandler = new BasicHttpAuthenticationHandler (username , password );
62
- this .jiraClient = new AsynchronousJiraRestClient (serverUri , getHttpClient (serverUri , authenticationHandler ));
61
+ this .httpClient = getHttpClient (serverUri , authenticationHandler );
62
+ this .jiraClient = new AsynchronousJiraRestClient (serverUri , httpClient );
63
63
}
64
64
65
65
@@ -72,22 +72,51 @@ public class JiraIntegrationFeatures implements IntegrationFeatures {
72
72
*/
73
73
public CimIssueType getIssueType (String projectKey , String issueTypeName ) {
74
74
return projectIssueTypeCache .computeIfAbsent (projectKey , k -> new HashMap <>())
75
- .computeIfAbsent (issueTypeName ,
76
- i -> StreamSupport . stream ( getCimProject ( projectKey ).getIssueTypes (). spliterator (), false )
77
- .filter (t -> Objects .equals (i , t .getName ()))
78
- .findFirst ()
79
- .orElseThrow (() -> new IllegalStateException (
80
- String .format ("IssueType '%s' not found in project '%s'" , issueTypeName , projectKey ))));
75
+ .computeIfAbsent (issueTypeName ,
76
+ i -> getIssueTypes ( projectKey ).stream ( )
77
+ .filter (t -> Objects .equals (i , t .getName ()))
78
+ .findFirst ()
79
+ .orElseThrow (() -> new IllegalStateException (
80
+ String .format ("IssueType '%s' not found in project '%s'" , issueTypeName , projectKey ))));
81
81
}
82
82
83
- private CimProject getCimProject (String projectKey ) {
84
- return cimProjectCache .computeIfAbsent (projectKey ,
85
- k -> StreamSupport .stream (jiraClient .getIssueClient ().getCreateIssueMetadata (new GetCreateIssueMetadataOptionsBuilder ()
86
- .withExpandedIssueTypesFields ()
87
- .withProjectKeys (k )
88
- .build ()).claim ().spliterator (), false )
89
- .findFirst ()
90
- .orElseThrow (() -> new IllegalStateException (String .format ("Project '%s' not found" , projectKey ))));
83
+ private final GenericJsonArrayParser <IssueType > issueTypesParser = GenericJsonArrayParser .create (new IssueTypeJsonParser ());
84
+ private final GenericJsonArrayParser <CimFieldInfo > CIM_ISSUE_TYPE_JSON_PARSER = GenericJsonArrayParser .create (new CimFieldsInfoJsonParser ());
85
+
86
+
87
+ private List <CimIssueType > getIssueTypes (String projectKey ) {
88
+ // calling the Jira REST API directly as JIRA v. 9.x dropped support for createmeta endpoint (https://confluence.atlassian.com/jiracore/preparing-for-jira-9-0-1115661092.html)
89
+ try {
90
+ String uri = serverUri + "/rest/api/2/issue/createmeta/" + projectKey + "/issuetypes/" ;
91
+ Response issueTypes = httpClient .newRequest (uri ).get ().get ();
92
+ PageJsonParser <IssueType > pages = new PageJsonParser <>(issueTypesParser );
93
+ Page <IssueType > values = pages .parse (new JSONObject (issueTypes .getEntity ()));
94
+ return StreamSupport .stream (values .getValues ().spliterator (), false ).map (i -> {
95
+ try {
96
+ Response cimIssueTypeRaw = httpClient .newRequest (uri + i .getId ()).get ().get ();
97
+ PageJsonParser <CimFieldInfo > cimPages = new PageJsonParser <>(CIM_ISSUE_TYPE_JSON_PARSER );
98
+ JSONObject json = new JSONObject (cimIssueTypeRaw .getEntity ());
99
+ Page <CimFieldInfo > cimValues = cimPages .parse (json );
100
+ AtomicInteger index = new AtomicInteger (0 );
101
+ JSONArray array = json .getJSONArray ("values" );
102
+ Map <String , CimFieldInfo > fields = StreamSupport .stream (cimValues .getValues ().spliterator (), false ).map (c -> {
103
+ // taking the fieldId directly from JSON as it is not bound to the CimFieldInfo object
104
+ try {
105
+ String id = ((JSONObject ) array .get (index .getAndIncrement ())).get ("fieldId" ).toString ();
106
+ return new CimFieldInfo (id , c .isRequired (), c .getName (), c .getSchema (), c .getOperations (), c .getAllowedValues (), c .getAutoCompleteUri ());
107
+ } catch (JSONException e ) {
108
+ throw new RuntimeException (e );
109
+ }
110
+ }).collect (Collectors .toMap (CimFieldInfo ::getName , f -> f ));
111
+
112
+ return new CimIssueType (i .getIconUri (), i .getId (), i .getName (), i .isSubtask (), i .getDescription (), i .getIconUri (), fields );
113
+ } catch (Exception e ) {
114
+ throw new RuntimeException (e );
115
+ }
116
+ }).collect (Collectors .toList ());
117
+ } catch (Exception e ) {
118
+ throw new RuntimeException (e );
119
+ }
91
120
}
92
121
93
122
/**
@@ -99,13 +128,13 @@ private CimProject getCimProject(String projectKey) {
99
128
*/
100
129
public BasicComponent getComponent (String projectKey , String componentName ) {
101
130
return StreamSupport .stream (projectCache .computeIfAbsent (projectKey ,
102
- p -> jiraClient .getProjectClient ().getProject (p ).claim ())
103
- .getComponents ()
104
- .spliterator (), false )
105
- .filter (i -> Objects .equals (i .getName (), componentName ))
106
- .findFirst ()
107
- .orElseThrow (
108
- () -> new IllegalStateException (String .format ("Component '%s' not found in project '%s'" , componentName , projectKey )));
131
+ p -> jiraClient .getProjectClient ().getProject (p ).claim ())
132
+ .getComponents ()
133
+ .spliterator (), false )
134
+ .filter (i -> Objects .equals (i .getName (), componentName ))
135
+ .findFirst ()
136
+ .orElseThrow (
137
+ () -> new IllegalStateException (String .format ("Component '%s' not found in project '%s'" , componentName , projectKey )));
109
138
}
110
139
111
140
/**
@@ -137,8 +166,8 @@ public List<Issue> searchForIssues(String jqlQuery) {
137
166
*/
138
167
public List <String > searchForIssueKeys (String jqlQuery ) {
139
168
return searchForIssues (jqlQuery , Collections .emptySet ())
140
- .stream ().map (BasicIssue ::getKey )
141
- .collect (Collectors .toList ());
169
+ .stream ().map (BasicIssue ::getKey )
170
+ .collect (Collectors .toList ());
142
171
}
143
172
144
173
/**
@@ -175,11 +204,11 @@ private List<Issue> searchForIssues(String jqlQuery, Set<String> fields) {
175
204
private List <Issue > searchForIssues (String jqlQuery , Integer limit , Integer startAt , Set <String > fields ) {
176
205
try {
177
206
return StreamSupport .stream (jiraClient .getSearchClient ()
178
- .searchJql (jqlQuery , limit , startAt , fields )
179
- .claim ()
180
- .getIssues ()
181
- .spliterator (), false )
182
- .collect (Collectors .toList ());
207
+ .searchJql (jqlQuery , limit , startAt , fields )
208
+ .claim ()
209
+ .getIssues ()
210
+ .spliterator (), false )
211
+ .collect (Collectors .toList ());
183
212
} catch (Exception e ) {
184
213
throw new RuntimeException (String .format ("Failed to execute query '%s'" , jqlQuery ), e );
185
214
}
0 commit comments