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