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.endpoint;
23
24 import static org.opencastproject.index.service.util.JSONUtils.safeString;
25 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
26
27 import org.opencastproject.index.service.resources.list.query.ServicesListQuery;
28 import org.opencastproject.index.service.util.RestUtils;
29 import org.opencastproject.serviceregistry.api.HostRegistration;
30 import org.opencastproject.serviceregistry.api.ServiceRegistry;
31 import org.opencastproject.serviceregistry.api.ServiceState;
32 import org.opencastproject.serviceregistry.api.ServiceStatistics;
33 import org.opencastproject.util.SmartIterator;
34 import org.opencastproject.util.doc.rest.RestParameter;
35 import org.opencastproject.util.doc.rest.RestQuery;
36 import org.opencastproject.util.doc.rest.RestResponse;
37 import org.opencastproject.util.doc.rest.RestService;
38 import org.opencastproject.util.requests.SortCriterion;
39 import org.opencastproject.util.requests.SortCriterion.Order;
40
41 import com.google.gson.JsonObject;
42
43 import org.apache.commons.lang3.StringUtils;
44 import org.json.simple.JSONAware;
45 import org.json.simple.JSONObject;
46 import org.osgi.service.component.annotations.Activate;
47 import org.osgi.service.component.annotations.Component;
48 import org.osgi.service.component.annotations.Reference;
49 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Optional;
60 import java.util.concurrent.TimeUnit;
61
62 import javax.servlet.http.HttpServletResponse;
63 import javax.ws.rs.GET;
64 import javax.ws.rs.Path;
65 import javax.ws.rs.Produces;
66 import javax.ws.rs.QueryParam;
67 import javax.ws.rs.core.MediaType;
68 import javax.ws.rs.core.Response;
69
70 @Path("/admin-ng/services")
71 @RestService(name = "ServicesProxyService", title = "UI Services",
72 abstractText = "This service provides the services data for the UI.",
73 notes = { "These Endpoints deliver informations about the services required for the UI.",
74 "<strong>Important:</strong> "
75 + "<em>This service is for exclusive use by the module admin-ui. Its API might change "
76 + "anytime without prior notice. Any dependencies other than the admin UI will be strictly ignored. "
77 + "DO NOT use this for integration of third-party applications.<em>"})
78 @Component(
79 immediate = true,
80 service = ServicesEndpoint.class,
81 property = {
82 "service.description=Admin UI - Services facade Endpoint",
83 "opencast.service.type=org.opencastproject.adminui.endpoint.ServicesEndpoint",
84 "opencast.service.path=/admin-ng/services"
85 }
86 )
87 @JaxrsResource
88 public class ServicesEndpoint {
89 private static final Logger logger = LoggerFactory.getLogger(ServicesEndpoint.class);
90 private ServiceRegistry serviceRegistry;
91
92 private static final String SERVICE_STATUS_TRANSLATION_PREFIX = "SYSTEMS.SERVICES.STATUS.";
93
94
95 @GET
96 @Path("services.json")
97 @Produces(MediaType.APPLICATION_JSON)
98 @RestQuery(description = "Returns the list of services", name = "services", restParameters = {
99 @RestParameter(name = "limit", description = "The maximum number of items to return per page", isRequired = false, type = RestParameter.Type.INTEGER),
100 @RestParameter(name = "offset", description = "The offset", isRequired = false, type = RestParameter.Type.INTEGER),
101 @RestParameter(name = "filter", description = "Filter results by name, host, actions, status or free text query", isRequired = false, type = STRING),
102 @RestParameter(name = "sort", description = "The sort order. May include any "
103 + "of the following: host, name, running, queued, completed, meanRunTime, meanQueueTime, "
104 + "status. The sort suffix must be :asc for ascending sort order and :desc for descending.", isRequired = false, type = STRING)
105 }, responses = { @RestResponse(description = "Returns the list of services from Opencast", responseCode = HttpServletResponse.SC_OK) }, returnDescription = "The list of services")
106 public Response getServices(@QueryParam("limit") final int limit, @QueryParam("offset") final int offset,
107 @QueryParam("filter") String filter, @QueryParam("sort") String sort) throws Exception {
108
109 Optional<String> sortOpt = Optional.ofNullable(StringUtils.trimToNull(sort));
110 ServicesListQuery query = new ServicesListQuery();
111 EndpointUtil.addRequestFiltersToQuery(filter, query);
112
113 String fName = null;
114 if (query.getName().isPresent())
115 fName = StringUtils.trimToNull(query.getName().get());
116 String fHostname = null;
117 if (query.getHostname().isPresent())
118 fHostname = StringUtils.trimToNull(query.getHostname().get());
119 String fNodeName = null;
120 if (query.getNodeName().isPresent())
121 fNodeName = StringUtils.trimToNull(query.getNodeName().get());
122 String fStatus = null;
123 if (query.getStatus().isPresent())
124 fStatus = StringUtils.trimToNull(query.getStatus().get());
125 String fFreeText = null;
126 if (query.getFreeText().isPresent())
127 fFreeText = StringUtils.trimToNull(query.getFreeText().get());
128
129 List<HostRegistration> servers = serviceRegistry.getHostRegistrations();
130 List<Service> services = new ArrayList<Service>();
131 for (ServiceStatistics stats : serviceRegistry.getServiceStatistics()) {
132 Service service = new Service(stats, findServerByHost(stats.getServiceRegistration().getHost(), servers));
133 if (fName != null && !StringUtils.equalsIgnoreCase(service.getName(), fName))
134 continue;
135
136 if (fHostname != null && !StringUtils.equalsIgnoreCase(service.getHost(), fHostname))
137 continue;
138
139 if (fNodeName != null && !StringUtils.equalsIgnoreCase(service.getNodeName(), fNodeName))
140 continue;
141
142 if (fStatus != null && !StringUtils.equalsIgnoreCase(service.getStatus().toString(), fStatus))
143 continue;
144
145 if (query.getActions().isPresent()) {
146 ServiceState serviceState = service.getStatus();
147
148 if (query.getActions().get()) {
149 if (ServiceState.NORMAL == serviceState)
150 continue;
151 } else {
152 if (ServiceState.NORMAL != serviceState)
153 continue;
154 }
155 }
156
157 if (fFreeText != null && !StringUtils.containsIgnoreCase(service.getName(), fFreeText)
158 && !StringUtils.containsIgnoreCase(service.getHost(), fFreeText)
159 && !StringUtils.containsIgnoreCase(service.getNodeName(), fFreeText)
160 && !StringUtils.containsIgnoreCase(service.getStatus().toString(), fFreeText))
161 continue;
162
163 services.add(service);
164 }
165 int total = services.size();
166
167 if (sortOpt.isPresent()) {
168 ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(sortOpt.get());
169 if (!sortCriteria.isEmpty()) {
170 try {
171 SortCriterion sortCriterion = sortCriteria.iterator().next();
172 Collections.sort(services, new ServiceStatisticsComparator(
173 sortCriterion.getFieldName(),
174 sortCriterion.getOrder() == Order.Ascending));
175 } catch (Exception ex) {
176 logger.warn("Failed to sort services collection.", ex);
177 }
178 }
179 }
180
181 List<JsonObject> jsonList = new ArrayList<>();
182 List<Service> limitedServices = new SmartIterator<Service>(limit, offset).applyLimitAndOffset(services);
183
184 for (Service s : limitedServices) {
185 jsonList.add(s.toJSON());
186 }
187
188 return RestUtils.okJsonList(jsonList, offset, limit, total);
189 }
190
191
192
193
194 class Service implements JSONAware {
195
196 public static final String COMPLETED_NAME = "completed";
197
198 public static final String HOST_NAME = "hostname";
199
200 public static final String NODE_NAME = "nodeName";
201
202 public static final String MEAN_QUEUE_TIME_NAME = "meanQueueTime";
203
204 public static final String MEAN_RUN_TIME_NAME = "meanRunTime";
205
206 public static final String NAME_NAME = "name";
207
208 public static final String QUEUED_NAME = "queued";
209
210 public static final String RUNNING_NAME = "running";
211
212 public static final String STATUS_NAME = "status";
213
214 public static final String ONLINE_NAME = "online";
215
216 public static final String MAINTENANCE_NAME = "maintenance";
217
218
219 private final ServiceStatistics serviceStatistics;
220
221 private final Optional<HostRegistration> server;
222
223
224 Service(ServiceStatistics serviceStatistics, Optional<HostRegistration> server) {
225 this.serviceStatistics = serviceStatistics;
226 this.server = server;
227 }
228
229
230
231
232
233 public int getCompletedJobs() {
234 return serviceStatistics.getFinishedJobs();
235 }
236
237
238
239
240
241 public String getHost() {
242 return serviceStatistics.getServiceRegistration().getHost();
243 }
244
245
246
247
248
249 public String getNodeName() {
250 return server.isPresent() ? server.get().getNodeName() : "";
251 }
252
253
254
255
256
257 public long getMeanQueueTime() {
258 return TimeUnit.MILLISECONDS.toSeconds(serviceStatistics.getMeanQueueTime());
259 }
260
261
262
263
264
265 public long getMeanRunTime() {
266 return TimeUnit.MILLISECONDS.toSeconds(serviceStatistics.getMeanRunTime());
267 }
268
269
270
271
272
273 public String getName() {
274 return serviceStatistics.getServiceRegistration().getServiceType();
275 }
276
277
278
279
280
281 public int getQueuedJobs() {
282 return serviceStatistics.getQueuedJobs();
283 }
284
285
286
287
288
289 public int getRunningJobs() {
290 return serviceStatistics.getRunningJobs();
291 }
292
293
294
295
296
297 public ServiceState getStatus() {
298 return serviceStatistics.getServiceRegistration().getServiceState();
299 }
300
301
302
303
304
305 public boolean getIsOnline() {
306 return serviceStatistics.getServiceRegistration().isOnline();
307 }
308
309
310
311
312
313 public boolean getisMaintenance() {
314 return serviceStatistics.getServiceRegistration().isInMaintenanceMode();
315 }
316
317
318
319
320
321 public Map<String, String> toMap() {
322 Map<String, String> serviceMap = new HashMap<String, String>();
323 serviceMap.put(COMPLETED_NAME, Integer.toString(getCompletedJobs()));
324 serviceMap.put(HOST_NAME, getHost());
325 serviceMap.put(NODE_NAME, getNodeName());
326 serviceMap.put(MEAN_QUEUE_TIME_NAME, Long.toString(getMeanQueueTime()));
327 serviceMap.put(MEAN_RUN_TIME_NAME, Long.toString(getMeanRunTime()));
328 serviceMap.put(NAME_NAME, getName());
329 serviceMap.put(QUEUED_NAME, Integer.toString(getQueuedJobs()));
330 serviceMap.put(RUNNING_NAME, Integer.toString(getRunningJobs()));
331 serviceMap.put(STATUS_NAME, getStatus().name());
332 serviceMap.put(ONLINE_NAME, Boolean.toString(getIsOnline()));
333 serviceMap.put(MAINTENANCE_NAME, Boolean.toString(getisMaintenance()));
334 return serviceMap;
335 }
336
337
338
339
340
341 @Override
342 public String toJSONString() {
343 return JSONObject.toJSONString(toMap());
344 }
345
346
347
348
349
350 public JsonObject toJSON() {
351 JsonObject json = new JsonObject();
352 json.addProperty(COMPLETED_NAME, getCompletedJobs());
353 json.addProperty(HOST_NAME, safeString(getHost()));
354 json.addProperty(NODE_NAME, safeString(getNodeName()));
355 json.addProperty(MEAN_QUEUE_TIME_NAME, getMeanQueueTime());
356 json.addProperty(MEAN_RUN_TIME_NAME, getMeanRunTime());
357 json.addProperty(NAME_NAME, safeString(getName()));
358 json.addProperty(QUEUED_NAME, getQueuedJobs());
359 json.addProperty(RUNNING_NAME, getRunningJobs());
360 json.addProperty(STATUS_NAME, SERVICE_STATUS_TRANSLATION_PREFIX + getStatus().name());
361 json.addProperty(ONLINE_NAME, getIsOnline());
362 json.addProperty(MAINTENANCE_NAME, getisMaintenance());
363 return json;
364 }
365 }
366
367
368
369
370 class ServiceStatisticsComparator implements Comparator<Service> {
371
372
373 private final String sortBy;
374
375 private final boolean ascending;
376
377
378 ServiceStatisticsComparator(String sortBy, boolean ascending) {
379 if (StringUtils.equalsIgnoreCase(Service.COMPLETED_NAME, sortBy)) {
380 this.sortBy = Service.COMPLETED_NAME;
381 } else if (StringUtils.equalsIgnoreCase(Service.HOST_NAME, sortBy)) {
382 this.sortBy = Service.HOST_NAME;
383 } else if (StringUtils.equalsIgnoreCase(Service.NODE_NAME, sortBy)) {
384 this.sortBy = Service.NODE_NAME;
385 } else if (StringUtils.equalsIgnoreCase(Service.MEAN_QUEUE_TIME_NAME, sortBy)) {
386 this.sortBy = Service.MEAN_QUEUE_TIME_NAME;
387 } else if (StringUtils.equalsIgnoreCase(Service.MEAN_RUN_TIME_NAME, sortBy)) {
388 this.sortBy = Service.MEAN_RUN_TIME_NAME;
389 } else if (StringUtils.equalsIgnoreCase(Service.NAME_NAME, sortBy)) {
390 this.sortBy = Service.NAME_NAME;
391 } else if (StringUtils.equalsIgnoreCase(Service.QUEUED_NAME, sortBy)) {
392 this.sortBy = Service.QUEUED_NAME;
393 } else if (StringUtils.equalsIgnoreCase(Service.RUNNING_NAME, sortBy)) {
394 this.sortBy = Service.RUNNING_NAME;
395 } else if (StringUtils.equalsIgnoreCase(Service.STATUS_NAME, sortBy)) {
396 this.sortBy = Service.STATUS_NAME;
397 } else {
398 throw new IllegalArgumentException(String.format("Can't sort services by %s.", sortBy));
399 }
400 this.ascending = ascending;
401 }
402
403
404
405
406
407
408
409 @Override
410 public int compare(Service s1, Service s2) {
411 int result = 0;
412 switch (sortBy) {
413 case Service.COMPLETED_NAME:
414 result = s1.getCompletedJobs() - s2.getCompletedJobs();
415 break;
416 case Service.HOST_NAME:
417 result = s1.getHost().compareToIgnoreCase(s2.getHost());
418 break;
419 case Service.NODE_NAME:
420 result = s1.getNodeName().compareToIgnoreCase(s2.getNodeName());
421 break;
422 case Service.MEAN_QUEUE_TIME_NAME:
423 result = (int) (s1.getMeanQueueTime() - s2.getMeanQueueTime());
424 break;
425 case Service.MEAN_RUN_TIME_NAME:
426 result = (int) (s1.getMeanRunTime() - s2.getMeanRunTime());
427 break;
428 case Service.QUEUED_NAME:
429 result = s1.getQueuedJobs() - s2.getQueuedJobs();
430 break;
431 case Service.RUNNING_NAME:
432 result = s1.getRunningJobs() - s2.getRunningJobs();
433 break;
434 case Service.STATUS_NAME:
435 result = s1.getStatus().compareTo(s2.getStatus());
436 break;
437 case Service.NAME_NAME:
438 default:
439 result = s1.getName().compareToIgnoreCase(s2.getName());
440 }
441 return ascending ? result : 0 - result;
442 }
443 }
444
445
446 @Activate
447 public void activate() {
448 logger.info("ServicesEndpoint is activated!");
449 }
450
451
452
453
454
455 @Reference
456 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
457 this.serviceRegistry = serviceRegistry;
458 }
459
460
461
462
463
464 private Optional<HostRegistration> findServerByHost(String hostname, List<HostRegistration> servers) {
465 return servers.stream().filter(o -> o.getBaseUrl().equals(hostname)).findFirst();
466 }
467 }