1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.adminui.tobira;
23
24 import org.json.simple.JSONArray;
25 import org.json.simple.JSONObject;
26 import org.json.simple.parser.JSONParser;
27 import org.json.simple.parser.ParseException;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import java.io.IOException;
32 import java.net.URI;
33 import java.net.URISyntaxException;
34 import java.net.http.HttpClient;
35 import java.net.http.HttpRequest;
36 import java.net.http.HttpResponse;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 public final class TobiraService {
42
43 public JSONObject getPage(String path) throws TobiraException {
44 return request(
45 "query AdminUiPage($path: String!) {"
46 + " page: realmByPath(path: $path) {"
47 + " ... RealmData"
48 + " children {"
49 + " ... RealmData"
50 + " blocks {"
51
52 + " id"
53 + " }"
54 + " }"
55 + " }"
56 + "}"
57 + "fragment RealmData on Realm {"
58 + " title: name"
59 + " segment: pathSegment"
60 + " path"
61 + "}",
62 Map.of("path", path));
63 }
64
65 public JSONObject getHostPages(String seriesId) throws TobiraException {
66 return (JSONObject) request(
67 "query AdminUIHostPages($seriesId: String!) {"
68 + " series: seriesByOpencastId(id: $seriesId) {"
69 + " id"
70 + " hostPages: hostRealms {"
71 + " ... RealmData"
72 + " blocks { id }"
73 + " ancestors {"
74 + " ... RealmData"
75 + " }"
76 + " }"
77 + " }"
78 + "}"
79 + "fragment RealmData on Realm {"
80 + " title: name"
81 + " segment: pathSegment"
82 + " path"
83 + "}",
84 Map.of("seriesId", seriesId))
85 .get("series");
86 }
87
88 public JSONObject getEventHostPages(String eventId) throws TobiraException {
89 return (JSONObject) request(
90 "query AdminUIEventHostPages($eventId: String!) {"
91 + " event: eventByOpencastId(id: $eventId) {"
92 + " ...on AuthorizedEvent {"
93 + " id"
94 + " hostPages: hostRealms {"
95 + " title: name"
96 + " path"
97 + " ancestors { title: name }"
98 + " }"
99 + " }"
100 + " }"
101 + "}",
102 Map.of("eventId", eventId))
103 .get("event");
104 }
105
106 public void mount(Map<String, Object> variables) throws TobiraException {
107 request(
108 "mutation AdminUIMountSeries($series: NewSeries!, $parentPagePath: String!, $newPages: [RealmSpecifier!]!) {"
109 + " mountSeries(series: $series, parentRealmPath: $parentPagePath, newRealms: $newPages) {"
110
111 + " id"
112 + " }"
113 + "}",
114 variables);
115 }
116
117 public Integer createRealmLineage(List<JSONObject> pathComponents) throws TobiraException {
118 return (Integer) request(
119 "mutation AdminUICreateRealmLineage($realms: [RealmLineageComponent!]!) {"
120 + " createRealmLineage(realms: $realms) { numCreated }"
121 + "}",
122 Map.of("realms", pathComponents))
123 .get("numCreated");
124 }
125
126 public String addSeriesMountPoint(Map<String, Object> variables) throws TobiraException {
127 return (String) request(
128 "mutation AdminUIAddSeriesMountPoint($seriesId: String!, $targetPath: String!) {"
129 + " addSeriesMountPoint(seriesOcId: $seriesId, targetPath: $targetPath) {"
130 + " id"
131 + " }"
132 + "}",
133 variables)
134 .get("id");
135 }
136
137 public JSONObject removeSeriesMountPoint(Map<String, Object> variables) throws TobiraException {
138 return (JSONObject) request(
139 "mutation AdminUIRemoveSeriesMountPoint($seriesId: String!, $currentPath: String!) {"
140 + " outcome: removeSeriesMountPoint(seriesOcId: $seriesId, path: $currentPath) {"
141 + " __typename"
142 + " }"
143 + "}",
144 variables)
145 .get("outcome");
146 }
147
148 public boolean ready() {
149 return this.endpoint != null && this.trustedKey != null;
150 }
151
152 private JSONObject request(String query, Map<String, Object> variables) throws TobiraException {
153
154 var queryObject = new JSONObject(Map.of(
155 "query", query,
156 "variables", new JSONObject(variables)));
157
158 var request = HttpRequest.newBuilder()
159 .uri(endpoint)
160 .header("content-type", "application/json")
161 .header("x-tobira-trusted-external-key", trustedKey)
162 .POST(HttpRequest.BodyPublishers.ofString(queryObject.toJSONString()))
163 .build();
164
165 try {
166 var response = client.send(request, HttpResponse.BodyHandlers.ofString());
167 if (response.statusCode() != 200) {
168 throw new TobiraException(response);
169 }
170 var responseObject = (JSONObject) new JSONParser().parse(response.body());
171 var errors = (JSONArray) responseObject.get("errors");
172 if (errors != null) {
173 throw new TobiraException(response, errors);
174 }
175 return (JSONObject) responseObject.get("data");
176 } catch (IOException | InterruptedException | ParseException e) {
177 throw new TobiraException(e);
178 }
179 }
180
181 public String getOrigin() {
182 return origin;
183 }
184
185 public void setOrigin(String origin) {
186 this.origin = origin;
187 if (origin == null) {
188 this.endpoint = null;
189 } else {
190 try {
191 this.endpoint = new URI(origin).resolve("/graphql");
192 } catch (URISyntaxException e) {
193 logger.error("Invalid Tobira origin {}", origin, e);
194 this.origin = null;
195 this.endpoint = null;
196 }
197 }
198 }
199
200 public void setTrustedKey(String trustedKey) {
201 this.trustedKey = trustedKey;
202 }
203
204 private String origin;
205 private URI endpoint;
206 private String trustedKey;
207
208 private static final HttpClient client = HttpClient.newBuilder()
209 .followRedirects(HttpClient.Redirect.NORMAL)
210 .build();
211
212 private static final Logger logger = LoggerFactory.getLogger(TobiraService.class);
213
214 private static Map<String, TobiraService> tobiras = new HashMap<>();
215
216 public static TobiraService getTobira(String organization) {
217 return tobiras.computeIfAbsent(organization, org -> new TobiraService());
218 }
219 }