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