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 java.lang.String.format;
25 import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
26 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
27 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
28 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
29 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
30 import static javax.servlet.http.HttpServletResponse.SC_OK;
31 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
32 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
33 import static javax.ws.rs.core.Response.Status.NOT_FOUND;
34 import static org.apache.commons.lang3.StringUtils.trimToNull;
35 import static org.opencastproject.adminui.endpoint.EndpointUtil.transformAccessControList;
36 import static org.opencastproject.index.service.impl.util.EventUtils.internalChannelFilter;
37 import static org.opencastproject.index.service.util.JSONUtils.arrayToJsonArray;
38 import static org.opencastproject.index.service.util.JSONUtils.collectionToJsonArray;
39 import static org.opencastproject.index.service.util.JSONUtils.mapToJsonObject;
40 import static org.opencastproject.index.service.util.JSONUtils.safeString;
41 import static org.opencastproject.index.service.util.RestUtils.conflictJson;
42 import static org.opencastproject.index.service.util.RestUtils.notFound;
43 import static org.opencastproject.index.service.util.RestUtils.notFoundJson;
44 import static org.opencastproject.index.service.util.RestUtils.okJson;
45 import static org.opencastproject.index.service.util.RestUtils.okJsonList;
46 import static org.opencastproject.index.service.util.RestUtils.serverErrorJson;
47 import static org.opencastproject.util.DateTimeSupport.toUTC;
48 import static org.opencastproject.util.RestUtil.R.badRequest;
49 import static org.opencastproject.util.RestUtil.R.conflict;
50 import static org.opencastproject.util.RestUtil.R.forbidden;
51 import static org.opencastproject.util.RestUtil.R.noContent;
52 import static org.opencastproject.util.RestUtil.R.notFound;
53 import static org.opencastproject.util.RestUtil.R.ok;
54 import static org.opencastproject.util.RestUtil.R.serverError;
55 import static org.opencastproject.util.doc.rest.RestParameter.Type.BOOLEAN;
56 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
57 import static org.opencastproject.util.doc.rest.RestParameter.Type.TEXT;
58
59 import org.opencastproject.adminui.exception.JobEndpointException;
60 import org.opencastproject.adminui.impl.AdminUIConfiguration;
61 import org.opencastproject.adminui.tobira.TobiraException;
62 import org.opencastproject.adminui.tobira.TobiraService;
63 import org.opencastproject.adminui.util.BulkUpdateUtil;
64 import org.opencastproject.assetmanager.api.AssetManager;
65 import org.opencastproject.authorization.xacml.manager.api.AclService;
66 import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
67 import org.opencastproject.authorization.xacml.manager.util.AccessInformationUtil;
68 import org.opencastproject.capture.CaptureParameters;
69 import org.opencastproject.capture.admin.api.Agent;
70 import org.opencastproject.capture.admin.api.CaptureAgentStateService;
71 import org.opencastproject.elasticsearch.api.SearchIndexException;
72 import org.opencastproject.elasticsearch.api.SearchResult;
73 import org.opencastproject.elasticsearch.api.SearchResultItem;
74 import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
75 import org.opencastproject.elasticsearch.index.objects.event.Event;
76 import org.opencastproject.elasticsearch.index.objects.event.EventIndexSchema;
77 import org.opencastproject.elasticsearch.index.objects.event.EventSearchQuery;
78 import org.opencastproject.event.comment.EventComment;
79 import org.opencastproject.event.comment.EventCommentException;
80 import org.opencastproject.event.comment.EventCommentReply;
81 import org.opencastproject.event.comment.EventCommentService;
82 import org.opencastproject.index.service.api.IndexService;
83 import org.opencastproject.index.service.api.IndexService.Source;
84 import org.opencastproject.index.service.exception.IndexServiceException;
85 import org.opencastproject.index.service.exception.UnsupportedAssetException;
86 import org.opencastproject.index.service.impl.util.EventUtils;
87 import org.opencastproject.index.service.resources.list.provider.EventsListProvider.Comments;
88 import org.opencastproject.index.service.resources.list.provider.EventsListProvider.IsPublished;
89 import org.opencastproject.index.service.resources.list.query.EventListQuery;
90 import org.opencastproject.index.service.resources.list.query.SeriesListQuery;
91 import org.opencastproject.index.service.util.JSONUtils;
92 import org.opencastproject.index.service.util.RestUtils;
93 import org.opencastproject.list.api.ListProviderException;
94 import org.opencastproject.list.api.ListProvidersService;
95 import org.opencastproject.list.api.ResourceListQuery;
96 import org.opencastproject.list.impl.ResourceListQueryImpl;
97 import org.opencastproject.mediapackage.Attachment;
98 import org.opencastproject.mediapackage.AudioStream;
99 import org.opencastproject.mediapackage.Catalog;
100 import org.opencastproject.mediapackage.MediaPackage;
101 import org.opencastproject.mediapackage.MediaPackageElement;
102 import org.opencastproject.mediapackage.MediaPackageException;
103 import org.opencastproject.mediapackage.Publication;
104 import org.opencastproject.mediapackage.Track;
105 import org.opencastproject.mediapackage.VideoStream;
106 import org.opencastproject.mediapackage.track.AudioStreamImpl;
107 import org.opencastproject.mediapackage.track.SubtitleStreamImpl;
108 import org.opencastproject.mediapackage.track.VideoStreamImpl;
109 import org.opencastproject.metadata.dublincore.DublinCore;
110 import org.opencastproject.metadata.dublincore.DublinCoreMetadataCollection;
111 import org.opencastproject.metadata.dublincore.EventCatalogUIAdapter;
112 import org.opencastproject.metadata.dublincore.MetadataField;
113 import org.opencastproject.metadata.dublincore.MetadataJson;
114 import org.opencastproject.metadata.dublincore.MetadataList;
115 import org.opencastproject.metadata.dublincore.MetadataList.Locked;
116 import org.opencastproject.rest.BulkOperationResult;
117 import org.opencastproject.rest.RestConstants;
118 import org.opencastproject.scheduler.api.Recording;
119 import org.opencastproject.scheduler.api.SchedulerException;
120 import org.opencastproject.scheduler.api.SchedulerService;
121 import org.opencastproject.scheduler.api.TechnicalMetadata;
122 import org.opencastproject.scheduler.api.Util;
123 import org.opencastproject.security.api.AccessControlList;
124 import org.opencastproject.security.api.AccessControlParser;
125 import org.opencastproject.security.api.AclScope;
126 import org.opencastproject.security.api.AuthorizationService;
127 import org.opencastproject.security.api.Organization;
128 import org.opencastproject.security.api.Permissions;
129 import org.opencastproject.security.api.SecurityService;
130 import org.opencastproject.security.api.UnauthorizedException;
131 import org.opencastproject.security.api.User;
132 import org.opencastproject.security.api.UserDirectoryService;
133 import org.opencastproject.security.urlsigning.exception.UrlSigningException;
134 import org.opencastproject.security.urlsigning.service.UrlSigningService;
135 import org.opencastproject.security.util.SecurityUtil;
136 import org.opencastproject.systems.OpencastConstants;
137 import org.opencastproject.util.DateTimeSupport;
138 import org.opencastproject.util.Jsons.Val;
139 import org.opencastproject.util.NotFoundException;
140 import org.opencastproject.util.RestUtil;
141 import org.opencastproject.util.UrlSupport;
142 import org.opencastproject.util.data.Tuple;
143 import org.opencastproject.util.data.Tuple3;
144 import org.opencastproject.util.doc.rest.RestParameter;
145 import org.opencastproject.util.doc.rest.RestQuery;
146 import org.opencastproject.util.doc.rest.RestResponse;
147 import org.opencastproject.util.requests.SortCriterion;
148 import org.opencastproject.workflow.api.RetryStrategy;
149 import org.opencastproject.workflow.api.WorkflowDatabaseException;
150 import org.opencastproject.workflow.api.WorkflowDefinition;
151 import org.opencastproject.workflow.api.WorkflowInstance;
152 import org.opencastproject.workflow.api.WorkflowOperationInstance;
153 import org.opencastproject.workflow.api.WorkflowService;
154 import org.opencastproject.workflow.api.WorkflowStateException;
155 import org.opencastproject.workflow.api.WorkflowUtil;
156
157 import com.google.gson.JsonArray;
158 import com.google.gson.JsonNull;
159 import com.google.gson.JsonObject;
160 import com.google.gson.JsonPrimitive;
161
162 import net.fortuna.ical4j.model.property.RRule;
163
164 import org.apache.commons.lang3.BooleanUtils;
165 import org.apache.commons.lang3.StringUtils;
166 import org.codehaus.jettison.json.JSONException;
167 import org.json.simple.JSONArray;
168 import org.json.simple.JSONObject;
169 import org.json.simple.parser.JSONParser;
170 import org.osgi.service.component.ComponentContext;
171 import org.osgi.service.component.annotations.Activate;
172 import org.slf4j.Logger;
173 import org.slf4j.LoggerFactory;
174
175 import java.net.URI;
176 import java.text.ParseException;
177 import java.time.Instant;
178 import java.util.ArrayList;
179 import java.util.Collections;
180 import java.util.Date;
181 import java.util.HashMap;
182 import java.util.HashSet;
183 import java.util.List;
184 import java.util.Map;
185 import java.util.Map.Entry;
186 import java.util.Objects;
187 import java.util.Optional;
188 import java.util.Set;
189 import java.util.TimeZone;
190 import java.util.function.Function;
191 import java.util.stream.Collectors;
192
193 import javax.servlet.http.HttpServletRequest;
194 import javax.servlet.http.HttpServletResponse;
195 import javax.ws.rs.Consumes;
196 import javax.ws.rs.DELETE;
197 import javax.ws.rs.FormParam;
198 import javax.ws.rs.GET;
199 import javax.ws.rs.POST;
200 import javax.ws.rs.PUT;
201 import javax.ws.rs.Path;
202 import javax.ws.rs.PathParam;
203 import javax.ws.rs.Produces;
204 import javax.ws.rs.QueryParam;
205 import javax.ws.rs.WebApplicationException;
206 import javax.ws.rs.core.Context;
207 import javax.ws.rs.core.MediaType;
208 import javax.ws.rs.core.Response;
209 import javax.ws.rs.core.Response.Status;
210
211
212
213
214
215
216
217
218 @Path("/admin-ng/event")
219 public abstract class AbstractEventEndpoint {
220
221
222
223
224 public static final String SCHEDULING_AGENT_ID_KEY = "agentId";
225 public static final String SCHEDULING_START_KEY = "start";
226 public static final String SCHEDULING_END_KEY = "end";
227 private static final String SCHEDULING_AGENT_CONFIGURATION_KEY = "agentConfiguration";
228 public static final String SCHEDULING_PREVIOUS_AGENTID = "previousAgentId";
229 public static final String SCHEDULING_PREVIOUS_PREVIOUSENTRIES = "previousEntries";
230
231 private static final String WORKFLOW_ACTION_STOP = "STOP";
232
233
234 static final Logger logger = LoggerFactory.getLogger(AbstractEventEndpoint.class);
235
236
237
238 protected static final String WORKFLOW_DEFINITION_DEFAULT = "org.opencastproject.workflow.default.definition";
239
240 private static final String WORKFLOW_STATUS_TRANSLATION_PREFIX = "EVENTS.EVENTS.DETAILS.WORKFLOWS.OPERATION_STATUS.";
241
242
243 protected static final long DEFAULT_URL_SIGNING_EXPIRE_DURATION = 2 * 60 * 60;
244
245 public abstract AssetManager getAssetManager();
246
247 public abstract WorkflowService getWorkflowService();
248
249 public abstract ElasticsearchIndex getIndex();
250
251 public abstract JobEndpoint getJobService();
252
253 public abstract SeriesEndpoint getSeriesEndpoint();
254
255 public abstract AclService getAclService();
256
257 public abstract EventCommentService getEventCommentService();
258
259 public abstract SecurityService getSecurityService();
260
261 public abstract IndexService getIndexService();
262
263 public abstract AuthorizationService getAuthorizationService();
264
265 public abstract SchedulerService getSchedulerService();
266
267 public abstract CaptureAgentStateService getCaptureAgentStateService();
268
269 public abstract AdminUIConfiguration getAdminUIConfiguration();
270
271 public abstract long getUrlSigningExpireDuration();
272
273 public abstract UrlSigningService getUrlSigningService();
274
275 public abstract Boolean signWithClientIP();
276
277 public abstract Boolean getOnlySeriesWithWriteAccessEventModal();
278
279 public abstract Boolean getOnlyEventsWithWriteAccessEventsTab();
280
281 public abstract UserDirectoryService getUserDirectoryService();
282
283 public abstract ListProvidersService getListProvidersService();
284
285
286 protected String serverUrl = "http://localhost:8080";
287
288
289 protected String serviceUrl = null;
290
291
292 protected String defaultWorkflowDefinionId = null;
293
294
295 private String systemUserName = "opencast_system_account";
296
297
298
299
300
301
302
303 @Activate
304 public void activate(ComponentContext cc) {
305 if (cc != null) {
306 String ccServerUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
307 if (StringUtils.isNotBlank(ccServerUrl)) {
308 this.serverUrl = ccServerUrl;
309 }
310
311 this.serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
312
313 String ccDefaultWorkflowDefinionId = StringUtils.trimToNull(cc.getBundleContext()
314 .getProperty(WORKFLOW_DEFINITION_DEFAULT));
315
316 if (StringUtils.isNotBlank(ccDefaultWorkflowDefinionId)) {
317 this.defaultWorkflowDefinionId = ccDefaultWorkflowDefinionId;
318 }
319
320 systemUserName = SecurityUtil.getSystemUserName(cc);
321 }
322 }
323
324
325 @POST
326 @Path("workflowProperties")
327 @Produces(MediaType.APPLICATION_JSON)
328 @RestQuery(
329 name = "workflowProperties",
330 description = "Returns workflow properties for the specified events",
331 returnDescription = "The workflow properties for every event as JSON",
332 restParameters = {
333 @RestParameter(name = "eventIds", description = "A JSON array of ids of the events", isRequired = true,
334 type = RestParameter.Type.STRING)},
335 responses = {
336 @RestResponse(description = "Returns the workflow properties for the events as JSON",
337 responseCode = HttpServletResponse.SC_OK),
338 @RestResponse(description = "The list of ids could not be parsed into a json list.",
339 responseCode = HttpServletResponse.SC_BAD_REQUEST)
340 })
341 public Response getEventWorkflowProperties(@FormParam("eventIds") String eventIds) throws UnauthorizedException {
342 if (StringUtils.isBlank(eventIds)) {
343 return Response.status(Response.Status.BAD_REQUEST).build();
344 }
345
346 JSONParser parser = new JSONParser();
347 List<String> ids;
348 try {
349 ids = (List<String>) parser.parse(eventIds);
350 } catch (org.json.simple.parser.ParseException e) {
351 logger.error("Unable to parse '{}'", eventIds, e);
352 return Response.status(Response.Status.BAD_REQUEST).build();
353 } catch (ClassCastException e) {
354 logger.error("Unable to cast '{}'", eventIds, e);
355 return Response.status(Response.Status.BAD_REQUEST).build();
356 }
357
358 final Map<String, Map<String, String>> eventWithProperties = getIndexService().getEventWorkflowProperties(ids);
359 JsonObject jsonEvents = new JsonObject();
360
361 for (Entry<String, Map<String, String>> event : eventWithProperties.entrySet()) {
362 JsonObject jsonProperties = new JsonObject();
363
364 for (Entry<String, String> property : event.getValue().entrySet()) {
365 jsonProperties.add(property.getKey(), new JsonPrimitive(property.getValue()));
366 }
367
368 jsonEvents.add(event.getKey(), jsonProperties);
369 }
370
371 return okJson(jsonEvents);
372 }
373
374
375 @GET
376 @Path("catalogAdapters")
377 @Produces(MediaType.APPLICATION_JSON)
378 @RestQuery(
379 name = "getcataloguiadapters",
380 description = "Returns the available catalog UI adapters as JSON",
381 returnDescription = "The catalog UI adapters as JSON",
382 responses = {
383 @RestResponse(description = "Returns the available catalog UI adapters as JSON",
384 responseCode = HttpServletResponse.SC_OK)
385 })
386 public Response getCatalogAdapters() {
387 JsonArray jsonAdapters = new JsonArray();
388 for (EventCatalogUIAdapter adapter : getIndexService().getEventCatalogUIAdapters()) {
389 JsonObject obj = new JsonObject();
390 obj.addProperty("flavor", adapter.getFlavor().toString());
391 obj.addProperty("title", adapter.getUITitle());
392 jsonAdapters.add(obj);
393 }
394
395 return okJson(jsonAdapters);
396 }
397
398 @GET
399 @Path("{eventId}")
400 @Produces(MediaType.APPLICATION_JSON)
401 @RestQuery(
402 name = "getevent",
403 description = "Returns the event by the given id as JSON",
404 returnDescription = "The event as JSON",
405 pathParameters = {
406 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
407 type = RestParameter.Type.STRING)
408 },
409 responses = {
410 @RestResponse(description = "Returns the event as JSON", responseCode = HttpServletResponse.SC_OK),
411 @RestResponse(description = "No event with this identifier was found.",
412 responseCode = HttpServletResponse.SC_NOT_FOUND)
413 })
414 public Response getEventResponse(@PathParam("eventId") String id) throws Exception {
415 Optional<Event> eventOpt = getIndexService().getEvent(id, getIndex());
416 if (eventOpt.isPresent()) {
417 Event event = eventOpt.get();
418 event.updatePreview(getAdminUIConfiguration().getPreviewSubtype());
419 JsonObject json = eventToJSON(event, Optional.empty(), Optional.empty());
420 return okJson(json);
421 }
422 return notFound("Cannot find an event with id '%s'.", id);
423 }
424
425 @DELETE
426 @Path("{eventId}")
427 @Produces(MediaType.APPLICATION_JSON)
428 @RestQuery(
429 name = "deleteevent",
430 description = "Delete a single event.",
431 returnDescription = "Ok if the event has been deleted.",
432 pathParameters = {
433 @RestParameter(name = "eventId", isRequired = true, description = "The id of the event to delete.",
434 type = STRING),
435 },
436 responses = {
437 @RestResponse(responseCode = SC_OK, description = "The event has been deleted."),
438 @RestResponse(responseCode = SC_ACCEPTED, description = "The event will be retracted and deleted "
439 + "afterwards."),
440 @RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "If the current user is not "
441 + "authorized to perform this action")
442 })
443 public Response deleteEvent(@PathParam("eventId") String id) throws UnauthorizedException, SearchIndexException {
444 final Optional<Event> event = checkAgentAccessForEvent(id);
445 if (event.isEmpty()) {
446 return RestUtil.R.notFound(id);
447 }
448 final IndexService.EventRemovalResult result;
449 try {
450 result = getIndexService().removeEvent(event.get(), getAdminUIConfiguration().getRetractWorkflowId());
451 } catch (WorkflowDatabaseException e) {
452 logger.error("Workflow database is not reachable. This may be a temporary problem.");
453 return RestUtil.R.serverError();
454 } catch (NotFoundException e) {
455 logger.error("Configured retract workflow not found. Check your configuration.");
456 return RestUtil.R.serverError();
457 }
458 switch (result) {
459 case SUCCESS:
460 return Response.ok().build();
461 case RETRACTING:
462 return Response.accepted().build();
463 case GENERAL_FAILURE:
464 return Response.serverError().build();
465 case NOT_FOUND:
466 return RestUtil.R.notFound(id);
467 default:
468 throw new RuntimeException("Unknown EventRemovalResult type: " + result.name());
469 }
470 }
471
472 @POST
473 @Path("deleteEvents")
474 @Produces(MediaType.APPLICATION_JSON)
475 @RestQuery(
476 name = "deleteevents",
477 description = "Deletes a json list of events by their given ids e.g. [\"1dbe7255-e17d-4279-811d-a5c7ced689bf\", "
478 + "\"04fae22b-0717-4f59-8b72-5f824f76d529\"]",
479 returnDescription = "Returns a JSON object containing a list of event ids that were deleted, not found or if "
480 + "there was a server error.",
481 responses = {
482 @RestResponse(description = "Events have been deleted", responseCode = HttpServletResponse.SC_OK),
483 @RestResponse(description = "The list of ids could not be parsed into a json list.",
484 responseCode = HttpServletResponse.SC_BAD_REQUEST),
485 @RestResponse(description = "If the current user is not authorized to perform this action",
486 responseCode = HttpServletResponse.SC_UNAUTHORIZED)
487 })
488 public Response deleteEvents(String eventIdsContent) throws UnauthorizedException, SearchIndexException {
489 if (StringUtils.isBlank(eventIdsContent)) {
490 return Response.status(Response.Status.BAD_REQUEST).build();
491 }
492
493 JSONParser parser = new JSONParser();
494 JSONArray eventIdsJsonArray;
495 try {
496 eventIdsJsonArray = (JSONArray) parser.parse(eventIdsContent);
497 } catch (org.json.simple.parser.ParseException e) {
498 logger.error("Unable to parse '{}'", eventIdsContent, e);
499 return Response.status(Response.Status.BAD_REQUEST).build();
500 } catch (ClassCastException e) {
501 logger.error("Unable to cast '{}'", eventIdsContent, e);
502 return Response.status(Response.Status.BAD_REQUEST).build();
503 }
504
505 BulkOperationResult result = new BulkOperationResult();
506
507 for (Object eventIdObject : eventIdsJsonArray) {
508 final String eventId = eventIdObject.toString();
509 try {
510 final Optional<Event> event = checkAgentAccessForEvent(eventId);
511 if (event.isPresent()) {
512 final IndexService.EventRemovalResult currentResult = getIndexService().removeEvent(event.get(),
513 getAdminUIConfiguration().getRetractWorkflowId());
514 switch (currentResult) {
515 case SUCCESS:
516 result.addOk(eventId);
517 break;
518 case RETRACTING:
519 result.addAccepted(eventId);
520 break;
521 case GENERAL_FAILURE:
522 result.addServerError(eventId);
523 break;
524 case NOT_FOUND:
525 result.addNotFound(eventId);
526 break;
527 default:
528 throw new RuntimeException("Unknown EventRemovalResult type: " + currentResult.name());
529 }
530 } else {
531 result.addNotFound(eventId);
532 }
533 } catch (UnauthorizedException e) {
534 result.addUnauthorized(eventId);
535 } catch (WorkflowDatabaseException e) {
536 logger.error("Workflow database is not reachable. This may be a temporary problem.");
537 return RestUtil.R.serverError();
538 } catch (NotFoundException e) {
539 logger.error("Configured retract workflow not found. Check your configuration.");
540 return RestUtil.R.serverError();
541 }
542 }
543 return Response.ok(result.toJson()).build();
544 }
545
546 @GET
547 @Path("{eventId}/publications.json")
548 @Produces(MediaType.APPLICATION_JSON)
549 @RestQuery(
550 name = "geteventpublications",
551 description = "Returns all the data related to the publications tab in the event details modal as JSON",
552 returnDescription = "All the data related to the event publications tab as JSON",
553 pathParameters = {
554 @RestParameter(name = "eventId", description = "The event id (mediapackage id).", isRequired = true,
555 type = RestParameter.Type.STRING)
556 },
557 responses = {
558 @RestResponse(description = "Returns all the data related to the event publications tab as JSON",
559 responseCode = HttpServletResponse.SC_OK),
560 @RestResponse(description = "No event with this identifier was found.",
561 responseCode = HttpServletResponse.SC_NOT_FOUND)
562 })
563 public Response getEventPublicationsTab(@PathParam("eventId") String id) throws Exception {
564 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
565 if (optEvent.isEmpty()) {
566 return notFound("Cannot find an event with id '%s'.", id);
567 }
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582 Event event = optEvent.get();
583
584
585 List<JsonObject> pubJSONList = eventPublicationsToJson(event);
586 JsonArray publicationsJsonArray = new JsonArray();
587 for (JsonObject pubJson : pubJSONList) {
588 publicationsJsonArray.add(pubJson);
589 }
590
591 JsonObject result = new JsonObject();
592 result.add("publications", publicationsJsonArray);
593
594
595 String startDate = event.getRecordingStartDate() != null
596 ? event.getRecordingStartDate().toString()
597 : "";
598 String endDate = event.getRecordingEndDate() != null
599 ? event.getRecordingEndDate().toString()
600 : "";
601 result.addProperty("start-date", startDate);
602 result.addProperty("end-date", endDate);
603
604 return okJson(result);
605 }
606
607 private List<JsonObject> eventPublicationsToJson(Event event) {
608 List<JsonObject> pubJSON = new ArrayList<>();
609
610 for (Publication publication : event.getPublications()) {
611 if (internalChannelFilter.test(publication)) {
612 pubJSON.add(publicationToJson.apply(publication));
613 }
614 }
615
616 return pubJSON;
617 }
618
619 private List<JsonObject> eventCommentsToJson(List<EventComment> comments) {
620 List<JsonObject> commentArr = new ArrayList<>();
621
622 for (EventComment c : comments) {
623 JsonObject author = new JsonObject();
624 author.addProperty("name", c.getAuthor().getName());
625 if (c.getAuthor().getEmail() != null) {
626 author.addProperty("email", c.getAuthor().getEmail());
627 } else {
628 author.add("email", null);
629 }
630 author.addProperty("username", c.getAuthor().getUsername());
631
632 JsonArray replies = new JsonArray();
633 List<JsonObject> replyJsonList = eventCommentRepliesToJson(c.getReplies());
634 for (JsonObject replyJson : replyJsonList) {
635 replies.add(replyJson);
636 }
637
638 JsonObject commentJson = new JsonObject();
639 commentJson.addProperty("reason", c.getReason());
640 commentJson.addProperty("resolvedStatus", c.isResolvedStatus());
641 commentJson.addProperty("modificationDate", c.getModificationDate().toInstant().toString());
642 commentJson.add("replies", replies);
643 commentJson.add("author", author);
644 commentJson.addProperty("id", c.getId().get());
645 commentJson.addProperty("text", c.getText());
646 commentJson.addProperty("creationDate", c.getCreationDate().toInstant().toString());
647
648 commentArr.add(commentJson);
649 }
650
651 return commentArr;
652 }
653
654 private List<JsonObject> eventCommentRepliesToJson(List<EventCommentReply> replies) {
655 List<JsonObject> repliesArr = new ArrayList<>();
656
657 for (EventCommentReply r : replies) {
658 JsonObject author = new JsonObject();
659 author.addProperty("name", r.getAuthor().getName());
660 if (r.getAuthor().getEmail() != null) {
661 author.addProperty("email", r.getAuthor().getEmail());
662 } else {
663 author.add("email", null);
664 }
665 author.addProperty("username", r.getAuthor().getUsername());
666
667 JsonObject replyJson = new JsonObject();
668 replyJson.addProperty("id", r.getId().get());
669 replyJson.addProperty("text", r.getText());
670 replyJson.addProperty("creationDate", r.getCreationDate().toInstant().toString());
671 replyJson.addProperty("modificationDate", r.getModificationDate().toInstant().toString());
672 replyJson.add("author", author);
673
674 repliesArr.add(replyJson);
675 }
676
677 return repliesArr;
678 }
679
680 @GET
681 @Path("{eventId}/scheduling.json")
682 @Produces(MediaType.APPLICATION_JSON)
683 @RestQuery(
684 name = "getEventSchedulingMetadata",
685 description = "Returns all of the scheduling metadata for an event",
686 returnDescription = "All the technical metadata related to scheduling as JSON",
687 pathParameters = {
688 @RestParameter(name = "eventId", description = "The event id (mediapackage id).", isRequired = true,
689 type = RestParameter.Type.STRING)
690 },
691 responses = {
692 @RestResponse(description = "Returns all the data related to the event scheduling tab as JSON",
693 responseCode = HttpServletResponse.SC_OK),
694 @RestResponse(description = "No event with this identifier was found.",
695 responseCode = HttpServletResponse.SC_NOT_FOUND)
696 })
697 public Response getEventScheduling(@PathParam("eventId") String eventId)
698 throws NotFoundException, UnauthorizedException, SearchIndexException {
699 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
700 if (optEvent.isEmpty()) {
701 return notFound("Cannot find an event with id '%s'.", eventId);
702 }
703
704 try {
705 TechnicalMetadata technicalMetadata = getSchedulerService().getTechnicalMetadata(eventId);
706 return okJson(technicalMetadataToJson(technicalMetadata));
707 } catch (SchedulerException e) {
708 logger.error("Unable to get technical metadata for event with id {}", eventId);
709 throw new WebApplicationException(e, SC_INTERNAL_SERVER_ERROR);
710 }
711 }
712
713 @POST
714 @Path("scheduling.json")
715 @Produces(MediaType.APPLICATION_JSON)
716 @RestQuery(
717 name = "getEventsScheduling",
718 description = "Returns all of the scheduling metadata for a list of events",
719 returnDescription = "All the technical metadata related to scheduling as JSON",
720 restParameters = {
721 @RestParameter(name = "eventIds", description = "An array of event IDs (mediapackage id)", isRequired = true,
722 type = RestParameter.Type.STRING),
723 @RestParameter(name = "ignoreNonScheduled", description = "Whether events that are not really scheduled "
724 + "events should be ignored or produce an error", isRequired = true, type = RestParameter.Type.BOOLEAN)
725 },
726 responses = {
727 @RestResponse(description = "Returns all the data related to the event scheduling tab as JSON",
728 responseCode = HttpServletResponse.SC_OK),
729 @RestResponse(description = "No event with this identifier was found.",
730 responseCode = HttpServletResponse.SC_NOT_FOUND)
731 })
732 public Response getEventsScheduling(@FormParam("eventIds") final List<String> eventIds,
733 @FormParam("ignoreNonScheduled") final boolean ignoreNonScheduled) {
734 JsonArray fields = new JsonArray();
735
736 for (final String eventId : eventIds) {
737 try {
738 fields.add(technicalMetadataToJson(getSchedulerService().getTechnicalMetadata(eventId)));
739 } catch (final NotFoundException e) {
740 if (!ignoreNonScheduled) {
741 logger.warn("Unable to find id {}", eventId, e);
742 return notFound("Cannot find an event with id '%s'.", eventId);
743 }
744 } catch (final UnauthorizedException e) {
745 logger.warn("Unauthorized access to event ID {}", eventId, e);
746 return Response.status(Status.BAD_REQUEST).build();
747 } catch (final SchedulerException e) {
748 logger.warn("Scheduler exception accessing event ID {}", eventId, e);
749 return Response.status(Status.BAD_REQUEST).build();
750 }
751 }
752 return okJson(fields);
753 }
754
755 @PUT
756 @Path("{eventId}/scheduling")
757 @RestQuery(
758 name = "updateEventScheduling",
759 description = "Updates the scheduling information of an event",
760 returnDescription = "The method doesn't return any content",
761 pathParameters = {
762 @RestParameter(name = "eventId", isRequired = true, description = "The event identifier",
763 type = RestParameter.Type.STRING)
764 },
765 restParameters = {
766 @RestParameter(name = "scheduling", isRequired = true, description = "The updated scheduling (JSON object)",
767 type = RestParameter.Type.TEXT)
768 },
769 responses = {
770 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The required params were missing in the "
771 + "request."),
772 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the event has not been found."),
773 @RestResponse(responseCode = SC_NO_CONTENT, description = "The method doesn't return any content")
774 })
775 public Response updateEventScheduling(@PathParam("eventId") String eventId,
776 @FormParam("scheduling") String scheduling)
777 throws NotFoundException, UnauthorizedException, SearchIndexException, IndexServiceException {
778 if (StringUtils.isBlank(scheduling)) {
779 return RestUtil.R.badRequest("Missing parameters");
780 }
781
782 try {
783 final Event event = getEventOrThrowNotFoundException(eventId);
784 updateEventScheduling(scheduling, event);
785 return Response.noContent().build();
786 } catch (JSONException e) {
787 return RestUtil.R.badRequest("The scheduling object is not valid");
788 } catch (ParseException e) {
789 return RestUtil.R.badRequest("The UTC dates in the scheduling object is not valid");
790 } catch (SchedulerException e) {
791 logger.error("Unable to update scheduling technical metadata of event {}", eventId, e);
792 throw new WebApplicationException(e, SC_INTERNAL_SERVER_ERROR);
793 } catch (IllegalStateException e) {
794 return RestUtil.R.badRequest(e.getMessage());
795 }
796 }
797
798 private void updateEventScheduling(String scheduling, Event event) throws NotFoundException, UnauthorizedException,
799 SchedulerException, JSONException, ParseException, SearchIndexException, IndexServiceException {
800 final TechnicalMetadata technicalMetadata = getSchedulerService().getTechnicalMetadata(event.getIdentifier());
801 final org.codehaus.jettison.json.JSONObject schedulingJson = new org.codehaus.jettison.json.JSONObject(
802 scheduling);
803 Optional<String> agentId = Optional.empty();
804 if (schedulingJson.has(SCHEDULING_AGENT_ID_KEY)) {
805 agentId = Optional.of(schedulingJson.getString(SCHEDULING_AGENT_ID_KEY));
806 logger.trace("Updating agent id of event '{}' from '{}' to '{}'",
807 event.getIdentifier(), technicalMetadata.getAgentId(), agentId);
808 }
809
810 Optional<String> previousAgentId = Optional.empty();
811 if (schedulingJson.has(SCHEDULING_PREVIOUS_AGENTID)) {
812 previousAgentId = Optional.of(schedulingJson.getString(SCHEDULING_PREVIOUS_AGENTID));
813 }
814
815 Optional<String> previousAgentInputs = Optional.empty();
816 Optional<String> agentInputs = Optional.empty();
817 if (agentId.isPresent() && previousAgentId.isPresent()) {
818 Agent previousAgent = getCaptureAgentStateService().getAgent(previousAgentId.get());
819 Agent agent = getCaptureAgentStateService().getAgent(agentId.get());
820
821 previousAgentInputs = Optional.ofNullable(previousAgent.getCapabilities().getProperty(
822 CaptureParameters.CAPTURE_DEVICE_NAMES));
823 agentInputs = Optional.ofNullable(agent.getCapabilities().getProperty(CaptureParameters.CAPTURE_DEVICE_NAMES));
824 }
825
826
827 checkAgentAccessForAgent(technicalMetadata.getAgentId());
828 if (agentId.isPresent()) {
829 checkAgentAccessForAgent(agentId.get());
830 }
831
832 Optional<Date> start = Optional.empty();
833 if (schedulingJson.has(SCHEDULING_START_KEY)) {
834 start = Optional.of(new Date(DateTimeSupport.fromUTC(schedulingJson.getString(SCHEDULING_START_KEY))));
835 logger.trace("Updating start time of event '{}' id from '{}' to '{}'",
836 event.getIdentifier(), DateTimeSupport.toUTC(technicalMetadata.getStartDate().getTime()),
837 DateTimeSupport.toUTC(start.get().getTime()));
838 }
839
840 Optional<Date> end = Optional.empty();
841 if (schedulingJson.has(SCHEDULING_END_KEY)) {
842 end = Optional.of(new Date(DateTimeSupport.fromUTC(schedulingJson.getString(SCHEDULING_END_KEY))));
843 logger.trace("Updating end time of event '{}' id from '{}' to '{}'",
844 event.getIdentifier(), DateTimeSupport.toUTC(technicalMetadata.getEndDate().getTime()),
845 DateTimeSupport.toUTC(end.get().getTime()));
846 }
847
848 Optional<Map<String, String>> agentConfiguration = Optional.empty();
849 if (schedulingJson.has(SCHEDULING_AGENT_CONFIGURATION_KEY)) {
850 agentConfiguration = Optional.of(JSONUtils.toMap(schedulingJson.getJSONObject(
851 SCHEDULING_AGENT_CONFIGURATION_KEY)));
852 logger.trace("Updating agent configuration of event '{}' id from '{}' to '{}'",
853 event.getIdentifier(), technicalMetadata.getCaptureAgentConfiguration(), agentConfiguration);
854 }
855
856 Optional<Map<String, String>> previousAgentInputMethods = Optional.empty();
857 if (schedulingJson.has(SCHEDULING_PREVIOUS_PREVIOUSENTRIES)) {
858 previousAgentInputMethods = Optional.of(
859 JSONUtils.toMap(schedulingJson.getJSONObject(SCHEDULING_PREVIOUS_PREVIOUSENTRIES)));
860 }
861
862
863
864 if (previousAgentInputs.isPresent() && previousAgentInputs.isPresent() && agentInputs.isPresent()) {
865 Map<String, String> map = previousAgentInputMethods.get();
866 String mapAsString = map.keySet().stream()
867 .collect(Collectors.joining(","));
868 String previousInputs = mapAsString;
869
870 if (previousAgentInputs.equals(agentInputs)) {
871 final Map<String, String> configMap = new HashMap<>(agentConfiguration.get());
872 configMap.put(CaptureParameters.CAPTURE_DEVICE_NAMES, previousInputs);
873 agentConfiguration = Optional.of(configMap);
874 }
875 }
876
877 if ((start.isPresent() || end.isPresent())
878 && end.orElse(technicalMetadata.getEndDate()).before(start.orElse(technicalMetadata.getStartDate()))) {
879 throw new IllegalStateException("The end date is before the start date");
880 }
881
882 if (!start.isEmpty() || !end.isEmpty() || !agentId.isEmpty() || !agentConfiguration.isEmpty()) {
883 getSchedulerService().updateEvent(event.getIdentifier(), start, end, agentId, Optional.empty(), Optional.empty(),
884 Optional.empty(), agentConfiguration);
885 }
886 }
887
888 private Event getEventOrThrowNotFoundException(final String eventId) throws NotFoundException, SearchIndexException {
889 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
890 if (optEvent.isPresent()) {
891 return optEvent.get();
892 } else {
893 throw new NotFoundException(format("Cannot find an event with id '%s'.", eventId));
894 }
895 }
896
897 @GET
898 @Path("{eventId}/comments")
899 @Produces(MediaType.APPLICATION_JSON)
900 @RestQuery(
901 name = "geteventcomments",
902 description = "Returns all the data related to the comments tab in the event details modal as JSON",
903 returnDescription = "All the data related to the event comments tab as JSON",
904 pathParameters = {
905 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
906 type = RestParameter.Type.STRING)
907 },
908 responses = {
909 @RestResponse(description = "Returns all the data related to the event comments tab as JSON",
910 responseCode = HttpServletResponse.SC_OK),
911 @RestResponse(description = "No event with this identifier was found.",
912 responseCode = HttpServletResponse.SC_NOT_FOUND)
913 })
914 public Response getEventComments(@PathParam("eventId") String eventId) throws Exception {
915 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
916 if (optEvent.isEmpty()) {
917 return notFound("Cannot find an event with id '%s'.", eventId);
918 }
919
920 try {
921 List<EventComment> comments = getEventCommentService().getComments(eventId);
922 List<Val> commentArr = new ArrayList<>();
923 for (EventComment c : comments) {
924 commentArr.add(c.toJson());
925 }
926 return Response.ok(org.opencastproject.util.Jsons.arr(commentArr).toJson(), MediaType.APPLICATION_JSON_TYPE)
927 .build();
928 } catch (EventCommentException e) {
929 logger.error("Unable to get comments from event {}", eventId, e);
930 throw new WebApplicationException(e);
931 }
932 }
933
934 @GET
935 @Path("{eventId}/hasActiveTransaction")
936 @Produces(MediaType.TEXT_PLAIN)
937 @RestQuery(
938 name = "hasactivetransaction",
939 description = "Returns whether there is currently a transaction in progress for the given event",
940 returnDescription = "Whether there is currently a transaction in progress for the given event",
941 pathParameters = {
942 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
943 type = RestParameter.Type.STRING)
944 },
945 responses = {
946 @RestResponse(description = "Returns whether there is currently a transaction in progress for the given "
947 + "event", responseCode = HttpServletResponse.SC_OK),
948 @RestResponse(description = "No event with this identifier was found.",
949 responseCode = HttpServletResponse.SC_NOT_FOUND)
950 })
951 public Response hasActiveTransaction(@PathParam("eventId") String eventId) throws Exception {
952 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
953 if (optEvent.isEmpty()) {
954 return notFound("Cannot find an event with id '%s'.", eventId);
955 }
956
957 JSONObject json = new JSONObject();
958
959 if (WorkflowUtil.isActive(optEvent.get().getWorkflowState())) {
960 json.put("active", true);
961 } else {
962 json.put("active", false);
963 }
964
965 return Response.ok(json.toJSONString()).build();
966 }
967
968 @GET
969 @Produces(MediaType.APPLICATION_JSON)
970 @Path("{eventId}/comment/{commentId}")
971 @RestQuery(
972 name = "geteventcomment",
973 description = "Returns the comment with the given identifier",
974 returnDescription = "Returns the comment as JSON",
975 pathParameters = {
976 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
977 type = RestParameter.Type.STRING),
978 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier", type = STRING)
979 },
980 responses = {
981 @RestResponse(responseCode = SC_OK, description = "The comment as JSON."),
982 @RestResponse(responseCode = SC_NOT_FOUND, description = "No event or comment with this identifier was "
983 + "found.")
984 })
985 public Response getEventComment(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId)
986 throws NotFoundException, Exception {
987 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
988 if (optEvent.isEmpty()) {
989 return notFound("Cannot find an event with id '%s'.", eventId);
990 }
991
992 try {
993 EventComment comment = getEventCommentService().getComment(commentId);
994 return Response.ok(comment.toJson().toJson()).build();
995 } catch (NotFoundException e) {
996 throw e;
997 } catch (Exception e) {
998 logger.error("Could not retrieve comment {}", commentId, e);
999 throw new WebApplicationException(e);
1000 }
1001 }
1002
1003 @PUT
1004 @Path("{eventId}/comment/{commentId}")
1005 @RestQuery(
1006 name = "updateeventcomment",
1007 description = "Updates an event comment",
1008 returnDescription = "The updated comment as JSON.",
1009 pathParameters = {
1010 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1011 type = RestParameter.Type.STRING),
1012 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier", type = STRING)
1013 },
1014 restParameters = {
1015 @RestParameter(name = "text", isRequired = false, description = "The comment text", type = TEXT),
1016 @RestParameter(name = "reason", isRequired = false, description = "The comment reason", type = STRING),
1017 @RestParameter(name = "resolved", isRequired = false, description = "The comment resolved status",
1018 type = RestParameter.Type.BOOLEAN)
1019 },
1020 responses = {
1021 @RestResponse(responseCode = SC_NOT_FOUND, description = "The event or comment to update has not been "
1022 + "found."),
1023 @RestResponse(responseCode = SC_OK, description = "The updated comment as JSON.")
1024 })
1025 public Response updateEventComment(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId,
1026 @FormParam("text") String text, @FormParam("reason") String reason, @FormParam("resolved") Boolean resolved)
1027 throws Exception {
1028 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1029 if (optEvent.isEmpty()) {
1030 return notFound("Cannot find an event with id '%s'.", eventId);
1031 }
1032
1033 try {
1034 EventComment dto = getEventCommentService().getComment(commentId);
1035
1036 if (StringUtils.isNotBlank(text)) {
1037 text = text.trim();
1038 } else {
1039 text = dto.getText();
1040 }
1041
1042 if (StringUtils.isNotBlank(reason)) {
1043 reason = reason.trim();
1044 } else {
1045 reason = dto.getReason();
1046 }
1047
1048 if (resolved == null) {
1049 resolved = dto.isResolvedStatus();
1050 }
1051
1052 EventComment updatedComment = EventComment.create(dto.getId(), eventId,
1053 getSecurityService().getOrganization().getId(), text, dto.getAuthor(), reason, resolved,
1054 dto.getCreationDate(), new Date(), dto.getReplies());
1055
1056 updatedComment = getEventCommentService().updateComment(updatedComment);
1057 List<EventComment> comments = getEventCommentService().getComments(eventId);
1058 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1059 return Response.ok(updatedComment.toJson().toJson()).build();
1060 } catch (NotFoundException e) {
1061 throw e;
1062 } catch (Exception e) {
1063 logger.error("Unable to update the comments catalog on event {}", eventId, e);
1064 throw new WebApplicationException(e);
1065 }
1066 }
1067
1068 @POST
1069 @Path("{eventId}/access")
1070 @RestQuery(
1071 name = "applyAclToEvent",
1072 description = "Immediate application of an ACL to an event",
1073 returnDescription = "Status code",
1074 pathParameters = {
1075 @RestParameter(name = "eventId", isRequired = true, description = "The event ID", type = STRING)
1076 },
1077 restParameters = {
1078 @RestParameter(name = "acl", isRequired = true, description = "The ACL to apply", type = STRING)
1079 },
1080 responses = {
1081 @RestResponse(responseCode = SC_OK, description = "The ACL has been successfully applied"),
1082 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Unable to parse the given ACL"),
1083 @RestResponse(responseCode = SC_NOT_FOUND, description = "The the event has not been found"),
1084 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "Not authorized to perform this action"),
1085 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Internal error")
1086 })
1087 public Response applyAclToEvent(@PathParam("eventId") String eventId, @FormParam("acl") String acl)
1088 throws NotFoundException, UnauthorizedException, SearchIndexException, IndexServiceException {
1089 final AccessControlList accessControlList;
1090 try {
1091 accessControlList = AccessControlParser.parseAcl(acl);
1092 } catch (Exception e) {
1093 logger.warn("Unable to parse ACL '{}'", acl);
1094 return badRequest();
1095 }
1096
1097 try {
1098 final Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1099 if (optEvent.isEmpty()) {
1100 logger.warn("Unable to find the event '{}'", eventId);
1101 return notFound();
1102 }
1103
1104 Source eventSource = getIndexService().getEventSource(optEvent.get());
1105 if (eventSource == Source.ARCHIVE) {
1106 Optional<MediaPackage> mediaPackage = getAssetManager().getMediaPackage(eventId);
1107 Optional<AccessControlList> aclOpt = Optional.ofNullable(accessControlList);
1108
1109 if (mediaPackage.isPresent()) {
1110 MediaPackage episodeSvcMp = mediaPackage.get();
1111 aclOpt.ifPresentOrElse(
1112 aclPresent -> {
1113 try {
1114 MediaPackage mp = getAuthorizationService()
1115 .setAcl(episodeSvcMp, AclScope.Episode, aclPresent)
1116 .getA();
1117 getAssetManager().takeSnapshot(mp);
1118 } catch (MediaPackageException e) {
1119 logger.error("Error getting ACL from media package", e);
1120 }
1121 },
1122 () -> {
1123 MediaPackage mp = getAuthorizationService().removeAcl(episodeSvcMp, AclScope.Episode);
1124 getAssetManager().takeSnapshot(mp);
1125 }
1126 );
1127 return ok();
1128 }
1129 logger.warn("Unable to find the event '{}'", eventId);
1130 return notFound();
1131 } else if (eventSource == Source.WORKFLOW) {
1132 logger.warn("An ACL cannot be edited while an event is part of a current workflow because it might"
1133 + " lead to inconsistent ACLs i.e. changed after distribution so that the old ACL is still "
1134 + "being used by the distribution channel.");
1135 JSONObject json = new JSONObject();
1136 json.put("Error", "Unable to edit an ACL for a current workflow.");
1137 return conflict(json.toJSONString());
1138 } else {
1139 MediaPackage mediaPackage = getIndexService().getEventMediapackage(optEvent.get());
1140 mediaPackage = getAuthorizationService().setAcl(mediaPackage, AclScope.Episode, accessControlList).getA();
1141
1142 getSchedulerService().updateEvent(eventId, Optional.empty(), Optional.empty(), Optional.empty(),
1143 Optional.empty(), Optional.of(mediaPackage), Optional.empty(), Optional.empty());
1144 return ok();
1145 }
1146 } catch (MediaPackageException e) {
1147 if (e.getCause() instanceof UnauthorizedException) {
1148 return forbidden();
1149 }
1150 logger.error("Error applying acl '{}' to event '{}'", accessControlList, eventId, e);
1151 return serverError();
1152 } catch (SchedulerException e) {
1153 logger.error("Error applying ACL to scheduled event {}", eventId, e);
1154 return serverError();
1155 }
1156 }
1157
1158 @POST
1159 @Path("{eventId}/comment")
1160 @Produces(MediaType.APPLICATION_JSON)
1161 @RestQuery(
1162 name = "createeventcomment",
1163 description = "Creates a comment related to the event given by the identifier",
1164 returnDescription = "The comment related to the event as JSON",
1165 pathParameters = {
1166 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1167 type = RestParameter.Type.STRING)
1168 },
1169 restParameters = {
1170 @RestParameter(name = "text", isRequired = true, description = "The comment text", type = TEXT),
1171 @RestParameter(name = "resolved", isRequired = false, description = "The comment resolved status",
1172 type = RestParameter.Type.BOOLEAN),
1173 @RestParameter(name = "reason", isRequired = false, description = "The comment reason", type = STRING)
1174 },
1175 responses = {
1176 @RestResponse(description = "The comment has been created.", responseCode = HttpServletResponse.SC_CREATED),
1177 @RestResponse(description = "If no text ist set.", responseCode = HttpServletResponse.SC_BAD_REQUEST),
1178 @RestResponse(description = "No event with this identifier was found.",
1179 responseCode = HttpServletResponse.SC_NOT_FOUND)
1180 })
1181 public Response createEventComment(@PathParam("eventId") String eventId, @FormParam("text") String text,
1182 @FormParam("reason") String reason, @FormParam("resolved") Boolean resolved) throws Exception {
1183 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1184 if (optEvent.isEmpty()) {
1185 return notFound("Cannot find an event with id '%s'.", eventId);
1186 }
1187
1188 if (StringUtils.isBlank(text)) {
1189 return Response.status(Status.BAD_REQUEST).build();
1190 }
1191
1192 User author = getSecurityService().getUser();
1193 try {
1194 EventComment createdComment = EventComment.create(Optional.<Long> empty(), eventId,
1195 getSecurityService().getOrganization().getId(), text, author, reason, BooleanUtils.toBoolean(reason));
1196 createdComment = getEventCommentService().updateComment(createdComment);
1197 List<EventComment> comments = getEventCommentService().getComments(eventId);
1198 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1199 return Response.created(getCommentUrl(eventId, createdComment.getId().get()))
1200 .entity(createdComment.toJson().toJson()).build();
1201 } catch (Exception e) {
1202 logger.error("Unable to create a comment on the event {}", eventId, e);
1203 throw new WebApplicationException(e);
1204 }
1205 }
1206
1207 @POST
1208 @Path("{eventId}/comment/{commentId}")
1209 @RestQuery(
1210 name = "resolveeventcomment",
1211 description = "Resolves an event comment",
1212 returnDescription = "The resolved comment.",
1213 pathParameters = {
1214 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1215 type = RestParameter.Type.STRING),
1216 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier", type = STRING)
1217 },
1218 responses = {
1219 @RestResponse(responseCode = SC_NOT_FOUND, description = "The event or comment to resolve has not been "
1220 + "found."),
1221 @RestResponse(responseCode = SC_OK, description = "The resolved comment as JSON.")
1222 })
1223 public Response resolveEventComment(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId)
1224 throws Exception {
1225 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1226 if (optEvent.isEmpty()) {
1227 return notFound("Cannot find an event with id '%s'.", eventId);
1228 }
1229
1230 try {
1231 EventComment dto = getEventCommentService().getComment(commentId);
1232 EventComment updatedComment = EventComment.create(dto.getId(), dto.getEventId(), dto.getOrganization(),
1233 dto.getText(), dto.getAuthor(), dto.getReason(), true, dto.getCreationDate(), new Date(),
1234 dto.getReplies());
1235
1236 updatedComment = getEventCommentService().updateComment(updatedComment);
1237 List<EventComment> comments = getEventCommentService().getComments(eventId);
1238 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1239 return Response.ok(updatedComment.toJson().toJson()).build();
1240 } catch (NotFoundException e) {
1241 throw e;
1242 } catch (Exception e) {
1243 logger.error("Could not resolve comment {}", commentId, e);
1244 throw new WebApplicationException(e);
1245 }
1246 }
1247
1248 @DELETE
1249 @Path("{eventId}/comment/{commentId}")
1250 @Produces(MediaType.APPLICATION_JSON)
1251 @RestQuery(
1252 name = "deleteeventcomment",
1253 description = "Deletes a event related comment by its identifier",
1254 returnDescription = "No content",
1255 pathParameters = {
1256 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1257 type = RestParameter.Type.STRING),
1258 @RestParameter(name = "commentId", description = "The comment id", isRequired = true,
1259 type = RestParameter.Type.STRING)
1260 },
1261 responses = {
1262 @RestResponse(description = "The event related comment has been deleted.",
1263 responseCode = HttpServletResponse.SC_NO_CONTENT),
1264 @RestResponse(description = "No event or comment with this identifier was found.",
1265 responseCode = HttpServletResponse.SC_NOT_FOUND)
1266 })
1267 public Response deleteEventComment(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId)
1268 throws Exception {
1269 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1270 if (optEvent.isEmpty()) {
1271 return notFound("Cannot find an event with id '%s'.", eventId);
1272 }
1273
1274 try {
1275 getEventCommentService().deleteComment(commentId);
1276 List<EventComment> comments = getEventCommentService().getComments(eventId);
1277 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1278 return Response.noContent().build();
1279 } catch (NotFoundException e) {
1280 throw e;
1281 } catch (Exception e) {
1282 logger.error("Unable to delete comment {} on event {}", commentId, eventId, e);
1283 throw new WebApplicationException(e);
1284 }
1285 }
1286
1287 @DELETE
1288 @Path("{eventId}/comment/{commentId}/{replyId}")
1289 @RestQuery(
1290 name = "deleteeventreply",
1291 description = "Delete an event comment reply",
1292 returnDescription = "The updated comment as JSON.",
1293 pathParameters = {
1294 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1295 type = RestParameter.Type.STRING),
1296 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier",
1297 type = STRING),
1298 @RestParameter(name = "replyId", isRequired = true, description = "The comment reply identifier",
1299 type = STRING)
1300 },
1301 responses = {
1302 @RestResponse(responseCode = SC_NOT_FOUND, description = "No event comment or reply with this identifier was "
1303 + "found."),
1304 @RestResponse(responseCode = SC_OK, description = "The updated comment as JSON.")
1305 })
1306 public Response deleteEventCommentReply(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId,
1307 @PathParam("replyId") long replyId) throws Exception {
1308 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1309 if (optEvent.isEmpty()) {
1310 return notFound("Cannot find an event with id '%s'.", eventId);
1311 }
1312
1313 EventComment comment = null;
1314 EventCommentReply reply = null;
1315 try {
1316 comment = getEventCommentService().getComment(commentId);
1317 for (EventCommentReply r : comment.getReplies()) {
1318 if (r.getId().isEmpty() || replyId != r.getId().get().longValue()) {
1319 continue;
1320 }
1321 reply = r;
1322 break;
1323 }
1324
1325 if (reply == null) {
1326 throw new NotFoundException("Reply with id " + replyId + " not found!");
1327 }
1328
1329 comment.removeReply(reply);
1330
1331 EventComment updatedComment = getEventCommentService().updateComment(comment);
1332 List<EventComment> comments = getEventCommentService().getComments(eventId);
1333 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1334 return Response.ok(updatedComment.toJson().toJson()).build();
1335 } catch (NotFoundException e) {
1336 throw e;
1337 } catch (Exception e) {
1338 logger.warn("Could not remove event comment reply {} from comment {}", replyId, commentId, e);
1339 throw new WebApplicationException(e);
1340 }
1341 }
1342
1343 @PUT
1344 @Path("{eventId}/comment/{commentId}/{replyId}")
1345 @RestQuery(
1346 name = "updateeventcommentreply",
1347 description = "Updates an event comment reply",
1348 returnDescription = "The updated comment as JSON.",
1349 pathParameters = {
1350 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1351 type = RestParameter.Type.STRING),
1352 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier",
1353 type = STRING),
1354 @RestParameter(name = "replyId", isRequired = true, description = "The comment reply identifier",
1355 type = STRING)
1356 },
1357 restParameters = {
1358 @RestParameter(name = "text", isRequired = true, description = "The comment reply text", type = TEXT)
1359 },
1360 responses = {
1361 @RestResponse(responseCode = SC_NOT_FOUND, description = "The event or comment to extend with a reply or the "
1362 + "reply has not been found."),
1363 @RestResponse(responseCode = HttpServletResponse.SC_BAD_REQUEST, description = "If no text is set."),
1364 @RestResponse(responseCode = SC_OK, description = "The updated comment as JSON.")
1365 })
1366 public Response updateEventCommentReply(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId,
1367 @PathParam("replyId") long replyId, @FormParam("text") String text) throws Exception {
1368 if (StringUtils.isBlank(text)) {
1369 return Response.status(Status.BAD_REQUEST).build();
1370 }
1371
1372 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1373 if (optEvent.isEmpty()) {
1374 return notFound("Cannot find an event with id '%s'.", eventId);
1375 }
1376
1377 EventComment comment = null;
1378 EventCommentReply reply = null;
1379 try {
1380 comment = getEventCommentService().getComment(commentId);
1381 for (EventCommentReply r : comment.getReplies()) {
1382 if (r.getId().isEmpty() || replyId != r.getId().get().longValue()) {
1383 continue;
1384 }
1385 reply = r;
1386 break;
1387 }
1388
1389 if (reply == null) {
1390 throw new NotFoundException("Reply with id " + replyId + " not found!");
1391 }
1392
1393 EventCommentReply updatedReply = EventCommentReply.create(reply.getId(), text.trim(), reply.getAuthor(),
1394 reply.getCreationDate(), new Date());
1395 comment.removeReply(reply);
1396 comment.addReply(updatedReply);
1397
1398 EventComment updatedComment = getEventCommentService().updateComment(comment);
1399 List<EventComment> comments = getEventCommentService().getComments(eventId);
1400 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1401 return Response.ok(updatedComment.toJson().toJson()).build();
1402 } catch (NotFoundException e) {
1403 throw e;
1404 } catch (Exception e) {
1405 logger.warn("Could not update event comment reply {} from comment {}", replyId, commentId, e);
1406 throw new WebApplicationException(e);
1407 }
1408 }
1409
1410 @POST
1411 @Path("{eventId}/comment/{commentId}/reply")
1412 @RestQuery(
1413 name = "createeventcommentreply",
1414 description = "Creates an event comment reply",
1415 returnDescription = "The updated comment as JSON.",
1416 pathParameters = {
1417 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1418 type = RestParameter.Type.STRING),
1419 @RestParameter(name = "commentId", isRequired = true, description = "The comment identifier",
1420 type = STRING)
1421 },
1422 restParameters = {
1423 @RestParameter(name = "text", isRequired = true, description = "The comment reply text", type = TEXT),
1424 @RestParameter(name = "resolved", isRequired = false, description = "Flag defining if this reply solve or "
1425 + "not the comment.", type = BOOLEAN)
1426 },
1427 responses = {
1428 @RestResponse(responseCode = SC_NOT_FOUND, description = "The event or comment to extend with a reply has "
1429 + "not been found."),
1430 @RestResponse(responseCode = HttpServletResponse.SC_BAD_REQUEST, description = "If no text is set."),
1431 @RestResponse(responseCode = SC_OK, description = "The updated comment as JSON.")
1432 })
1433 public Response createEventCommentReply(@PathParam("eventId") String eventId, @PathParam("commentId") long commentId,
1434 @FormParam("text") String text, @FormParam("resolved") Boolean resolved) throws Exception {
1435 if (StringUtils.isBlank(text)) {
1436 return Response.status(Status.BAD_REQUEST).build();
1437 }
1438
1439 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1440 if (optEvent.isEmpty()) {
1441 return notFound("Cannot find an event with id '%s'.", eventId);
1442 }
1443
1444 EventComment comment = null;
1445 try {
1446 comment = getEventCommentService().getComment(commentId);
1447 EventComment updatedComment;
1448
1449 if (resolved != null && resolved) {
1450
1451 updatedComment = EventComment.create(comment.getId(), comment.getEventId(), comment.getOrganization(),
1452 comment.getText(), comment.getAuthor(), comment.getReason(), true, comment.getCreationDate(),
1453 new Date(), comment.getReplies());
1454 } else {
1455 updatedComment = comment;
1456 }
1457
1458 User author = getSecurityService().getUser();
1459 EventCommentReply reply = EventCommentReply.create(Optional.<Long> empty(), text, author);
1460 updatedComment.addReply(reply);
1461
1462 updatedComment = getEventCommentService().updateComment(updatedComment);
1463 List<EventComment> comments = getEventCommentService().getComments(eventId);
1464 getIndexService().updateCommentCatalog(optEvent.get(), comments);
1465 return Response.ok(updatedComment.toJson().toJson()).build();
1466 } catch (Exception e) {
1467 logger.warn("Could not create event comment reply on comment {}", comment, e);
1468 throw new WebApplicationException(e);
1469 }
1470 }
1471
1472
1473
1474
1475
1476 private void removeSeriesWithNullTitlesFromFieldCollection(MetadataList ml) {
1477
1478 MetadataField seriesField = Optional.ofNullable(ml.getMetadataList().get("dublincore/episode"))
1479 .flatMap(titledMetadataCollection -> Optional.ofNullable(titledMetadataCollection.getCollection()))
1480 .flatMap(dcMetadataCollection -> Optional.ofNullable(dcMetadataCollection.getOutputFields()))
1481 .flatMap(metadataFields -> Optional.ofNullable(metadataFields.get("isPartOf")))
1482 .orElse(null);
1483 if (seriesField == null || seriesField.getCollection() == null) {
1484 return;
1485 }
1486
1487
1488 Map<String, String> seriesCollection = seriesField.getCollection();
1489 seriesCollection.remove(null);
1490 seriesField.setCollection(seriesCollection);
1491
1492 return;
1493 }
1494
1495 @GET
1496 @Path("{eventId}/metadata.json")
1497 @Produces(MediaType.APPLICATION_JSON)
1498 @RestQuery(
1499 name = "geteventmetadata",
1500 description = "Returns all the data related to the metadata tab in the event details modal as JSON",
1501 returnDescription = "All the data related to the event metadata tab as JSON",
1502 pathParameters = {
1503 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1504 type = RestParameter.Type.STRING)
1505 },
1506 responses = {
1507 @RestResponse(description = "Returns all the data related to the event metadata tab as JSON",
1508 responseCode = HttpServletResponse.SC_OK),
1509 @RestResponse(description = "No event with this identifier was found.",
1510 responseCode = HttpServletResponse.SC_NOT_FOUND)
1511 })
1512 public Response getEventMetadata(@PathParam("eventId") String eventId) throws Exception {
1513 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1514 if (optEvent.isEmpty()) {
1515 return notFound("Cannot find an event with id '%s'.", eventId);
1516 }
1517 Event event = optEvent.get();
1518 MetadataList metadataList = new MetadataList();
1519
1520
1521 List<EventCatalogUIAdapter> extendedCatalogUIAdapters = getIndexService().getExtendedEventCatalogUIAdapters();
1522 if (!extendedCatalogUIAdapters.isEmpty()) {
1523 MediaPackage mediaPackage;
1524 try {
1525 mediaPackage = getIndexService().getEventMediapackage(event);
1526 } catch (IndexServiceException e) {
1527 if (e.getCause() instanceof NotFoundException) {
1528 return notFound("Cannot find data for event %s", eventId);
1529 } else if (e.getCause() instanceof UnauthorizedException) {
1530 return Response.status(Status.FORBIDDEN).entity("Not authorized to access " + eventId).build();
1531 }
1532 logger.error("Internal error when trying to access metadata for " + eventId, e);
1533 return serverError();
1534 }
1535
1536 for (EventCatalogUIAdapter extendedCatalogUIAdapter : extendedCatalogUIAdapters) {
1537 metadataList.add(extendedCatalogUIAdapter, extendedCatalogUIAdapter.getFields(mediaPackage));
1538 }
1539 }
1540
1541
1542
1543
1544 EventCatalogUIAdapter eventCatalogUiAdapter = getIndexService().getCommonEventCatalogUIAdapter();
1545 DublinCoreMetadataCollection metadataCollection = eventCatalogUiAdapter.getRawFields(getCollectionQueryDisable());
1546 EventUtils.setEventMetadataValues(event, metadataCollection);
1547 metadataList.add(eventCatalogUiAdapter, metadataCollection);
1548
1549
1550 removeSeriesWithNullTitlesFromFieldCollection(metadataList);
1551
1552
1553 final String wfState = event.getWorkflowState();
1554 if (wfState != null && WorkflowUtil.isActive(WorkflowInstance.WorkflowState.valueOf(wfState))) {
1555 metadataList.setLocked(Locked.WORKFLOW_RUNNING);
1556 }
1557
1558 return okJson(MetadataJson.listToJson(metadataList, true));
1559 }
1560
1561
1562
1563
1564
1565
1566
1567 private Map getCollectionQueryDisable() {
1568 HashMap<String, ResourceListQuery> collectionQueryOverrides = new HashMap();
1569 SeriesListQuery seriesListQuery = new SeriesListQuery();
1570 seriesListQuery.setLimit(0);
1571 collectionQueryOverrides.put(DublinCore.PROPERTY_IS_PART_OF.getLocalName(), seriesListQuery);
1572 return collectionQueryOverrides;
1573 }
1574
1575 @POST
1576 @Path("events/metadata.json")
1577 @Produces(MediaType.APPLICATION_JSON)
1578 @RestQuery(
1579 name = "geteventsmetadata",
1580 description = "Returns all the data related to the edit events metadata modal as JSON",
1581 returnDescription = "All the data related to the edit events metadata modal as JSON",
1582 restParameters = {
1583 @RestParameter(name = "eventIds", description = "The event ids", isRequired = true,
1584 type = RestParameter.Type.STRING)
1585 }, responses = {
1586 @RestResponse(description = "Returns all the data related to the edit events metadata modal as JSON",
1587 responseCode = HttpServletResponse.SC_OK),
1588 @RestResponse(description = "No events to update, either not found or with running workflow, details in "
1589 + "response body.", responseCode = HttpServletResponse.SC_NOT_FOUND)
1590 })
1591 public Response getEventsMetadata(@FormParam("eventIds") String eventIds) throws Exception {
1592 if (StringUtils.isBlank(eventIds)) {
1593 return badRequest("Event ids can't be empty");
1594 }
1595
1596 JSONParser parser = new JSONParser();
1597 List<String> ids;
1598 try {
1599 ids = (List<String>) parser.parse(eventIds);
1600 } catch (org.json.simple.parser.ParseException e) {
1601 logger.error("Unable to parse '{}'", eventIds, e);
1602 return badRequest("Unable to parse event ids");
1603 } catch (ClassCastException e) {
1604 logger.error("Unable to cast '{}'", eventIds, e);
1605 return badRequest("Unable to parse event ids");
1606 }
1607
1608 Set<String> eventsNotFound = new HashSet();
1609 Set<String> eventsWithRunningWorkflow = new HashSet();
1610 Set<String> eventsMerged = new HashSet();
1611
1612
1613 List<DublinCoreMetadataCollection> collectedMetadata = new ArrayList();
1614 for (String eventId: ids) {
1615 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1616
1617 if (optEvent.isEmpty()) {
1618 eventsNotFound.add(eventId);
1619 continue;
1620 }
1621
1622 Event event = optEvent.get();
1623
1624
1625 final String wfState = event.getWorkflowState();
1626 if (wfState != null && WorkflowUtil.isActive(WorkflowInstance.WorkflowState.valueOf(wfState))) {
1627 eventsWithRunningWorkflow.add(eventId);
1628 continue;
1629 }
1630
1631
1632 EventCatalogUIAdapter eventCatalogUiAdapter = getIndexService().getCommonEventCatalogUIAdapter();
1633 DublinCoreMetadataCollection metadataCollection = eventCatalogUiAdapter.getRawFields(
1634 getCollectionQueryDisable());
1635 EventUtils.setEventMetadataValues(event, metadataCollection);
1636 collectedMetadata.add(metadataCollection);
1637
1638 eventsMerged.add(eventId);
1639 }
1640
1641
1642 if (collectedMetadata.isEmpty()) {
1643 JsonObject response = new JsonObject();
1644 response.add("notFound", collectionToJsonArray(eventsNotFound));
1645 response.add("runningWorkflow", collectionToJsonArray(eventsWithRunningWorkflow));
1646 return Response.status(Status.NOT_FOUND).type(MediaType.APPLICATION_JSON_TYPE).build();
1647 }
1648
1649
1650 DublinCoreMetadataCollection mergedMetadata;
1651 if (collectedMetadata.size() == 1) {
1652 mergedMetadata = collectedMetadata.get(0);
1653 }
1654 else {
1655
1656 mergedMetadata = new DublinCoreMetadataCollection(collectedMetadata.get(0));
1657 collectedMetadata.remove(0);
1658
1659 for (MetadataField field : mergedMetadata.getFields()) {
1660 for (DublinCoreMetadataCollection otherMetadataCollection : collectedMetadata) {
1661 MetadataField matchingField = otherMetadataCollection.getOutputFields().get(field.getOutputID());
1662
1663
1664 if (!Objects.equals(field.getValue(), matchingField.getValue())) {
1665 field.setDifferentValues();
1666 break;
1667 }
1668 }
1669 }
1670 }
1671
1672 JsonObject result = new JsonObject();
1673 result.add("metadata", MetadataJson.collectionToJson(mergedMetadata, true));
1674 result.add("notFound", collectionToJsonArray(eventsNotFound));
1675 result.add("runningWorkflow", collectionToJsonArray(eventsWithRunningWorkflow));
1676 result.add("merged", collectionToJsonArray(eventsMerged));
1677
1678 return okJson(result);
1679 }
1680
1681 @PUT
1682 @Path("bulk/update")
1683 @RestQuery(
1684 name = "bulkupdate",
1685 description = "Update all of the given events at once",
1686 restParameters = {
1687 @RestParameter(name = "update", isRequired = true, type = RestParameter.Type.TEXT,
1688 description = "The list of groups with events and fields to update.")
1689 }, responses = {
1690 @RestResponse(description = "All events have been updated successfully.",
1691 responseCode = HttpServletResponse.SC_OK),
1692 @RestResponse(description = "Could not parse update instructions.",
1693 responseCode = HttpServletResponse.SC_BAD_REQUEST),
1694 @RestResponse(description = "Field updating metadata or scheduling information. Some events may have been "
1695 + "updated. Details are available in the response body.",
1696 responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR),
1697 @RestResponse(description = "The events in the response body were not found. No events were updated.",
1698 responseCode = HttpServletResponse.SC_NOT_FOUND)
1699 },
1700 returnDescription = "In case of success, no content is returned. In case of errors while updating the metadata "
1701 + "or scheduling information, the errors are returned. In case events were not found, their ids are returned")
1702 public Response bulkUpdate(@FormParam("update") String updateJson) {
1703
1704 final BulkUpdateUtil.BulkUpdateInstructions instructions;
1705 try {
1706 instructions = new BulkUpdateUtil.BulkUpdateInstructions(updateJson);
1707 } catch (IllegalArgumentException e) {
1708 return badRequest("Cannot parse bulk update instructions");
1709 }
1710
1711 final Map<String, String> metadataUpdateFailures = new HashMap<>();
1712 final Map<String, String> schedulingUpdateFailures = new HashMap<>();
1713
1714 for (final BulkUpdateUtil.BulkUpdateInstructionGroup groupInstructions : instructions.getGroups()) {
1715
1716 final Map<String, Optional<Event>> events = groupInstructions.getEventIds().stream()
1717 .collect(Collectors.toMap(id -> id, id -> BulkUpdateUtil.getEvent(getIndexService(), getIndex(), id)));
1718
1719
1720 final Set<String> notFoundIds = events.entrySet().stream()
1721 .filter(e -> !e.getValue().isPresent())
1722 .map(Entry::getKey)
1723 .collect(Collectors.toSet());
1724 if (!notFoundIds.isEmpty()) {
1725 return notFoundJson(collectionToJsonArray(notFoundIds));
1726 }
1727
1728
1729 events.values().forEach(e -> e.ifPresent(event -> {
1730
1731 JSONObject metadata = null;
1732
1733
1734 try {
1735 if (groupInstructions.getScheduling() != null) {
1736
1737 final JSONObject scheduling = BulkUpdateUtil.addSchedulingDates(event, groupInstructions.getScheduling());
1738 updateEventScheduling(scheduling.toJSONString(), event);
1739
1740 metadata = BulkUpdateUtil.toNonTechnicalMetadataJson(scheduling);
1741 }
1742 } catch (Exception exception) {
1743 schedulingUpdateFailures.put(event.getIdentifier(), exception.getMessage());
1744 }
1745
1746
1747 try {
1748 if (groupInstructions.getMetadata() != null || metadata != null) {
1749 metadata = BulkUpdateUtil.mergeMetadataFields(metadata, groupInstructions.getMetadata());
1750 getIndexService().updateAllEventMetadata(event.getIdentifier(),
1751 JSONArray.toJSONString(Collections.singletonList(metadata)), getIndex());
1752 }
1753 } catch (Exception exception) {
1754 metadataUpdateFailures.put(event.getIdentifier(), exception.getMessage());
1755 }
1756 }));
1757 }
1758
1759
1760 if (!metadataUpdateFailures.isEmpty() || !schedulingUpdateFailures.isEmpty()) {
1761 JsonObject json = new JsonObject();
1762 json.add("metadataFailures", mapToJsonObject(metadataUpdateFailures));
1763 json.add("schedulingFailures", mapToJsonObject(schedulingUpdateFailures));
1764 return serverErrorJson(json);
1765 }
1766 return ok();
1767 }
1768
1769 @POST
1770 @Path("bulk/conflicts")
1771 @RestQuery(
1772 name = "getBulkConflicts",
1773 description = "Checks if the current bulk update scheduling settings are in a conflict with another event",
1774 returnDescription = "Returns NO CONTENT if no event are in conflict within specified period or list of "
1775 + "conflicting recordings in JSON",
1776 restParameters = {
1777 @RestParameter(name = "update", isRequired = true, type = RestParameter.Type.TEXT, description = "The list "
1778 + "of events and fields to update.")
1779 },
1780 responses = {
1781 @RestResponse(responseCode = HttpServletResponse.SC_NO_CONTENT, description = "No conflicting events found"),
1782 @RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "The events in the response "
1783 + "body were not found. No events were updated."),
1784 @RestResponse(responseCode = HttpServletResponse.SC_CONFLICT, description = "There is a conflict"),
1785 @RestResponse(responseCode = HttpServletResponse.SC_BAD_REQUEST, description = "Missing or invalid "
1786 + "parameters")
1787 })
1788 public Response getBulkConflicts(@FormParam("update") final String updateJson) throws NotFoundException {
1789 final BulkUpdateUtil.BulkUpdateInstructions instructions;
1790 try {
1791 instructions = new BulkUpdateUtil.BulkUpdateInstructions(updateJson);
1792 } catch (IllegalArgumentException e) {
1793 return badRequest("Cannot parse bulk update instructions");
1794 }
1795
1796 final Map<String, List<JsonObject>> conflicts = new HashMap<>();
1797 final List<Tuple3<String, Optional<Event>, JSONObject>> eventsWithSchedulingOpt = instructions.getGroups().stream()
1798 .flatMap(group -> group.getEventIds().stream().map(eventId -> Tuple3
1799 .tuple3(eventId, BulkUpdateUtil.getEvent(getIndexService(), getIndex(), eventId), group.getScheduling())))
1800 .collect(Collectors.toList());
1801
1802 final Set<String> notFoundIds = eventsWithSchedulingOpt.stream().filter(e -> !e.getB().isPresent())
1803 .map(Tuple3::getA).collect(Collectors.toSet());
1804 if (!notFoundIds.isEmpty()) {
1805 return notFoundJson(collectionToJsonArray(notFoundIds));
1806 }
1807 final List<Tuple<Event, JSONObject>> eventsWithScheduling = eventsWithSchedulingOpt.stream()
1808 .map(e -> Tuple.tuple(e.getB().get(), e.getC())).collect(Collectors.toList());
1809 final Set<String> changedIds = eventsWithScheduling.stream().map(e -> e.getA().getIdentifier())
1810 .collect(Collectors.toSet());
1811 for (final Tuple<Event, JSONObject> eventWithGroup : eventsWithScheduling) {
1812 final Event event = eventWithGroup.getA();
1813 final JSONObject groupScheduling = eventWithGroup.getB();
1814 try {
1815 if (groupScheduling != null) {
1816
1817 final JSONObject scheduling = BulkUpdateUtil.addSchedulingDates(event, groupScheduling);
1818 final Date start = Date.from(Instant.parse((String) scheduling.get(SCHEDULING_START_KEY)));
1819 final Date end = Date.from(Instant.parse((String) scheduling.get(SCHEDULING_END_KEY)));
1820 final String agentId = Optional.ofNullable((String) scheduling.get(SCHEDULING_AGENT_ID_KEY))
1821 .orElse(event.getAgentId());
1822
1823 final List<JsonObject> currentConflicts = new ArrayList<>();
1824
1825
1826 eventsWithScheduling.stream()
1827 .filter(otherEvent -> !otherEvent.getA().getIdentifier().equals(event.getIdentifier()))
1828 .forEach(otherEvent -> {
1829 final JSONObject otherScheduling = BulkUpdateUtil.addSchedulingDates(otherEvent.getA(),
1830 otherEvent.getB());
1831 final Date otherStart = Date.from(Instant.parse((String) otherScheduling.get(SCHEDULING_START_KEY)));
1832 final Date otherEnd = Date.from(Instant.parse((String) otherScheduling.get(SCHEDULING_END_KEY)));
1833 final String otherAgentId = Optional.ofNullable((String) otherScheduling.get(SCHEDULING_AGENT_ID_KEY))
1834 .orElse(otherEvent.getA().getAgentId());
1835 if (!otherAgentId.equals(agentId)) {
1836
1837 return;
1838 }
1839 if (Util.schedulingIntervalsOverlap(start, end, otherStart, otherEnd)) {
1840
1841 currentConflicts.add(convertEventToConflictingObject(
1842 DateTimeSupport.toUTC(otherStart.getTime()),
1843 DateTimeSupport.toUTC(otherEnd.getTime()),
1844 otherEvent.getA().getTitle()));
1845 }
1846 });
1847
1848
1849 final List<MediaPackage> conflicting = getSchedulerService().findConflictingEvents(agentId, start, end)
1850 .stream()
1851 .filter(mp -> !changedIds.contains(mp.getIdentifier().toString()))
1852 .collect(Collectors.toList());
1853 if (!conflicting.isEmpty()) {
1854 currentConflicts.addAll(convertToConflictObjects(event.getIdentifier(), conflicting));
1855 }
1856 conflicts.put(event.getIdentifier(), currentConflicts);
1857 }
1858 } catch (final SchedulerException | UnauthorizedException | SearchIndexException exception) {
1859 throw new RuntimeException(exception);
1860 }
1861 }
1862
1863 if (!conflicts.isEmpty()) {
1864 JsonArray responseJson = new JsonArray();
1865
1866 conflicts.forEach((eventId, conflictingEvents) -> {
1867 if (!conflictingEvents.isEmpty()) {
1868 JsonObject obj = new JsonObject();
1869 obj.addProperty("eventId", eventId);
1870
1871 JsonArray conflictsArray = new JsonArray();
1872 for (JsonObject conflict : conflictingEvents) {
1873 conflictsArray.add(conflict);
1874 }
1875
1876 obj.add("conflicts", conflictsArray);
1877 responseJson.add(obj);
1878 }
1879 });
1880
1881 if (responseJson.size() > 0) {
1882 return conflictJson(responseJson);
1883 }
1884 }
1885
1886 return noContent();
1887 }
1888
1889 @PUT
1890 @Path("{eventId}/metadata")
1891 @RestQuery(
1892 name = "updateeventmetadata",
1893 description = "Update the passed metadata for the event with the given Id",
1894 pathParameters = {
1895 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
1896 type = RestParameter.Type.STRING)
1897 },
1898 restParameters = {
1899 @RestParameter(name = "metadata", isRequired = true, type = RestParameter.Type.TEXT,
1900 description = "The list of metadata to update")
1901 },
1902 responses = {
1903 @RestResponse(description = "The metadata have been updated.", responseCode = HttpServletResponse.SC_OK),
1904 @RestResponse(description = "Could not parse metadata.", responseCode = HttpServletResponse.SC_BAD_REQUEST),
1905 @RestResponse(description = "No event with this identifier was found.",
1906 responseCode = HttpServletResponse.SC_NOT_FOUND)
1907 },
1908 returnDescription = "No content is returned.")
1909 public Response updateEventMetadata(@PathParam("eventId") String id, @FormParam("metadata") String metadataJSON)
1910 throws Exception {
1911 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
1912 if (optEvent.isEmpty()) {
1913 return notFound("Cannot find an event with id '%s'.", id);
1914 }
1915
1916 try {
1917 MetadataList metadataList = getIndexService().updateAllEventMetadata(id, metadataJSON, getIndex());
1918 return okJson(MetadataJson.listToJson(metadataList, true));
1919 } catch (IllegalArgumentException e) {
1920 return badRequest(String.format("Event %s metadata can't be updated.: %s", id, e.getMessage()));
1921 }
1922 }
1923
1924 @PUT
1925 @Path("events/metadata")
1926 @RestQuery(
1927 name = "updateeventsmetadata",
1928 description = "Update the passed metadata for the events with the given ids",
1929 restParameters = {
1930 @RestParameter(name = "eventIds", isRequired = true, type = RestParameter.Type.STRING,
1931 description = "The ids of the events to update"),
1932 @RestParameter(name = "metadata", isRequired = true, type = RestParameter.Type.TEXT,
1933 description = "The metadata fields to update"),
1934 },
1935 responses = {
1936 @RestResponse(description = "All events have been updated successfully.",
1937 responseCode = HttpServletResponse.SC_NO_CONTENT),
1938 @RestResponse(description = "One or multiple errors occured while updating event metadata. "
1939 + "Some events may have been updated successfully. "
1940 + "Details are available in the response body.",
1941 responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
1942 },
1943 returnDescription = "In case of complete success, no content is returned. Otherwise, the response content "
1944 + "contains the ids of events that couldn't be found and the ids and errors of events where the update "
1945 + "failed as well as the ids of the events that were updated successfully.")
1946 public Response updateEventsMetadata(@FormParam("eventIds") String eventIds, @FormParam("metadata") String metadata)
1947 throws Exception {
1948
1949 if (StringUtils.isBlank(eventIds)) {
1950 return badRequest("Event ids can't be empty");
1951 }
1952
1953 JSONParser parser = new JSONParser();
1954 List<String> ids;
1955 try {
1956 ids = (List<String>) parser.parse(eventIds);
1957 } catch (org.json.simple.parser.ParseException e) {
1958 logger.error("Unable to parse '{}'", eventIds, e);
1959 return badRequest("Unable to parse event ids");
1960 } catch (ClassCastException e) {
1961 logger.error("Unable to cast '{}'", eventIds, e);
1962 return badRequest("Unable to parse event ids");
1963 }
1964
1965
1966 Set<String> eventsNotFound = new HashSet<>();
1967 Set<String> eventsUpdated = new HashSet<>();
1968 Set<String> eventsUpdateFailure = new HashSet();
1969
1970 for (String eventId : ids) {
1971 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
1972
1973
1974 if (optEvent.isEmpty()) {
1975 eventsNotFound.add(eventId);
1976 continue;
1977 }
1978
1979
1980 try {
1981 getIndexService().updateAllEventMetadata(eventId, metadata, getIndex());
1982 eventsUpdated.add(eventId);
1983 } catch (IllegalArgumentException e) {
1984 eventsUpdateFailure.add(eventId);
1985 }
1986 }
1987
1988
1989 if (!eventsNotFound.isEmpty() || !eventsUpdateFailure.isEmpty()) {
1990 JsonObject errorJson = new JsonObject();
1991
1992 errorJson.add("updateFailures", collectionToJsonArray(eventsUpdateFailure));
1993 errorJson.add("notFound", collectionToJsonArray(eventsNotFound));
1994 errorJson.add("updated", collectionToJsonArray(eventsUpdated));
1995
1996 return serverErrorJson(errorJson);
1997 }
1998
1999 return noContent();
2000 }
2001
2002 @GET
2003 @Path("{eventId}/asset/assets.json")
2004 @Produces(MediaType.APPLICATION_JSON)
2005 @RestQuery(
2006 name = "getAssetList",
2007 description = "Returns the number of assets from each types as JSON",
2008 returnDescription = "The number of assets from each types as JSON",
2009 pathParameters = {
2010 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2011 type = RestParameter.Type.STRING)
2012 },
2013 responses = {
2014 @RestResponse(description = "Returns the number of assets from each types as JSON",
2015 responseCode = HttpServletResponse.SC_OK),
2016 @RestResponse(description = "No event with this identifier was found.",
2017 responseCode = HttpServletResponse.SC_NOT_FOUND)
2018 })
2019 public Response getAssetList(@PathParam("eventId") String id) throws Exception {
2020 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2021 if (optEvent.isEmpty()) {
2022 return notFound("Cannot find an event with id '%s'.", id);
2023 }
2024 MediaPackage mp;
2025 try {
2026 mp = getIndexService().getEventMediapackage(optEvent.get());
2027 } catch (IndexServiceException e) {
2028 if (e.getCause() instanceof NotFoundException) {
2029 return notFound("Cannot find data for event %s", id);
2030 } else if (e.getCause() instanceof UnauthorizedException) {
2031 return Response.status(Status.FORBIDDEN).entity("Not authorized to access " + id).build();
2032 }
2033 logger.error("Internal error when trying to access metadata for " + id, e);
2034 return serverError();
2035 }
2036 int attachments = mp.getAttachments().length;
2037 int catalogs = mp.getCatalogs().length;
2038 int media = mp.getTracks().length;
2039 int publications = mp.getPublications().length;
2040
2041 JsonObject result = new JsonObject();
2042 result.addProperty("attachments", attachments);
2043 result.addProperty("catalogs", catalogs);
2044 result.addProperty("media", media);
2045 result.addProperty("publications", publications);
2046
2047 return okJson(result);
2048 }
2049
2050 @GET
2051 @Path("{eventId}/asset/attachment/attachments.json")
2052 @Produces(MediaType.APPLICATION_JSON)
2053 @RestQuery(
2054 name = "getAttachmentsList",
2055 description = "Returns a list of attachments from the given event as JSON",
2056 returnDescription = "The list of attachments from the given event as JSON",
2057 pathParameters = {
2058 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2059 type = RestParameter.Type.STRING)
2060 },
2061 responses = {
2062 @RestResponse(description = "Returns a list of attachments from the given event as JSON",
2063 responseCode = HttpServletResponse.SC_OK),
2064 @RestResponse(description = "No event with this identifier was found.",
2065 responseCode = HttpServletResponse.SC_NOT_FOUND)
2066 })
2067 public Response getAttachmentsList(@PathParam("eventId") String id) throws Exception {
2068 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2069 if (optEvent.isEmpty()) {
2070 return notFound("Cannot find an event with id '%s'.", id);
2071 }
2072 MediaPackage mp = getIndexService().getEventMediapackage(optEvent.get());
2073 return okJson(getEventMediaPackageElements(mp.getAttachments()));
2074 }
2075
2076 @GET
2077 @Path("{eventId}/asset/attachment/{id}.json")
2078 @Produces(MediaType.APPLICATION_JSON)
2079 @RestQuery(
2080 name = "getAttachment",
2081 description = "Returns the details of an attachment from the given event and attachment id as JSON",
2082 returnDescription = "The details of an attachment from the given event and attachment id as JSON",
2083 pathParameters = {
2084 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2085 type = RestParameter.Type.STRING),
2086 @RestParameter(name = "id", description = "The attachment id", isRequired = true,
2087 type = RestParameter.Type.STRING)
2088 },
2089 responses = {
2090 @RestResponse(description = "Returns the details of an attachment from the given event and attachment id as "
2091 + "JSON", responseCode = HttpServletResponse.SC_OK),
2092 @RestResponse(description = "No event or attachment with this identifier was found.",
2093 responseCode = HttpServletResponse.SC_NOT_FOUND)
2094 })
2095 public Response getAttachment(@PathParam("eventId") String eventId, @PathParam("id") String id)
2096 throws NotFoundException, SearchIndexException, IndexServiceException {
2097 MediaPackage mp = getMediaPackageByEventId(eventId);
2098
2099 Attachment attachment = mp.getAttachment(id);
2100 if (attachment == null) {
2101 return notFound("Cannot find an attachment with id '%s'.", id);
2102 }
2103 return okJson(attachmentToJSON(attachment));
2104 }
2105
2106 @GET
2107 @Path("{eventId}/asset/catalog/catalogs.json")
2108 @Produces(MediaType.APPLICATION_JSON)
2109 @RestQuery(
2110 name = "getCatalogList",
2111 description = "Returns a list of catalogs from the given event as JSON",
2112 returnDescription = "The list of catalogs from the given event as JSON",
2113 pathParameters = {
2114 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2115 type = RestParameter.Type.STRING)
2116 },
2117 responses = {
2118 @RestResponse(description = "Returns a list of catalogs from the given event as JSON",
2119 responseCode = HttpServletResponse.SC_OK),
2120 @RestResponse(description = "No event with this identifier was found.",
2121 responseCode = HttpServletResponse.SC_NOT_FOUND)
2122 })
2123 public Response getCatalogList(@PathParam("eventId") String id) throws Exception {
2124 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2125 if (optEvent.isEmpty()) {
2126 return notFound("Cannot find an event with id '%s'.", id);
2127 }
2128 MediaPackage mp = getIndexService().getEventMediapackage(optEvent.get());
2129 return okJson(getEventMediaPackageElements(mp.getCatalogs()));
2130 }
2131
2132 @GET
2133 @Path("{eventId}/asset/catalog/{id}.json")
2134 @Produces(MediaType.APPLICATION_JSON)
2135 @RestQuery(
2136 name = "getCatalog",
2137 description = "Returns the details of a catalog from the given event and catalog id as JSON",
2138 returnDescription = "The details of a catalog from the given event and catalog id as JSON",
2139 pathParameters = {
2140 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2141 type = RestParameter.Type.STRING),
2142 @RestParameter(name = "id", description = "The catalog id", isRequired = true,
2143 type = RestParameter.Type.STRING)
2144 },
2145 responses = {
2146 @RestResponse(description = "Returns the details of a catalog from the given event and catalog id as JSON",
2147 responseCode = HttpServletResponse.SC_OK),
2148 @RestResponse(description = "No event or catalog with this identifier was found.",
2149 responseCode = HttpServletResponse.SC_NOT_FOUND)
2150 })
2151 public Response getCatalog(@PathParam("eventId") String eventId, @PathParam("id") String id)
2152 throws NotFoundException, SearchIndexException, IndexServiceException {
2153 MediaPackage mp = getMediaPackageByEventId(eventId);
2154
2155 Catalog catalog = mp.getCatalog(id);
2156 if (catalog == null) {
2157 return notFound("Cannot find a catalog with id '%s'.", id);
2158 }
2159 return okJson(catalogToJSON(catalog));
2160 }
2161
2162 @GET
2163 @Path("{eventId}/asset/media/media.json")
2164 @Produces(MediaType.APPLICATION_JSON)
2165 @RestQuery(
2166 name = "getMediaList",
2167 description = "Returns a list of media from the given event as JSON",
2168 returnDescription = "The list of media from the given event as JSON",
2169 pathParameters = {
2170 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2171 type = RestParameter.Type.STRING)
2172 },
2173 responses = {
2174 @RestResponse(description = "Returns a list of media from the given event as JSON",
2175 responseCode = HttpServletResponse.SC_OK),
2176 @RestResponse(description = "No event with this identifier was found.",
2177 responseCode = HttpServletResponse.SC_NOT_FOUND)
2178 })
2179 public Response getMediaList(@PathParam("eventId") String id) throws Exception {
2180 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2181 if (optEvent.isEmpty()) {
2182 return notFound("Cannot find an event with id '%s'.", id);
2183 }
2184 MediaPackage mp = getIndexService().getEventMediapackage(optEvent.get());
2185 return okJson(getEventMediaPackageElements(mp.getTracks()));
2186 }
2187
2188 @GET
2189 @Path("{eventId}/asset/media/{id}.json")
2190 @Produces(MediaType.APPLICATION_JSON)
2191 @RestQuery(
2192 name = "getMedia",
2193 description = "Returns the details of a media from the given event and media id as JSON",
2194 returnDescription = "The details of a media from the given event and media id as JSON",
2195 pathParameters = {
2196 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2197 type = RestParameter.Type.STRING),
2198 @RestParameter(name = "id", description = "The media id", isRequired = true, type = RestParameter.Type.STRING)
2199 },
2200
2201 responses = {
2202 @RestResponse(description = "Returns the media of a catalog from the given event and media id as JSON",
2203 responseCode = HttpServletResponse.SC_OK),
2204 @RestResponse(description = "No event or media with this identifier was found.",
2205 responseCode = HttpServletResponse.SC_NOT_FOUND)
2206 })
2207 public Response getMedia(@PathParam("eventId") String eventId, @PathParam("id") String id)
2208 throws NotFoundException, SearchIndexException, IndexServiceException {
2209 MediaPackage mp = getMediaPackageByEventId(eventId);
2210
2211 Track track = mp.getTrack(id);
2212 if (track == null) {
2213 return notFound("Cannot find media with id '%s'.", id);
2214 }
2215 return okJson(trackToJSON(track));
2216 }
2217
2218 @GET
2219 @Path("{eventId}/asset/publication/publications.json")
2220 @Produces(MediaType.APPLICATION_JSON)
2221 @RestQuery(
2222 name = "getPublicationList",
2223 description = "Returns a list of publications from the given event as JSON",
2224 returnDescription = "The list of publications from the given event as JSON",
2225 pathParameters = {
2226 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2227 type = RestParameter.Type.STRING)
2228 },
2229 responses = {
2230 @RestResponse(description = "Returns a list of publications from the given event as JSON",
2231 responseCode = HttpServletResponse.SC_OK),
2232 @RestResponse(description = "No event with this identifier was found.",
2233 responseCode = HttpServletResponse.SC_NOT_FOUND)
2234 })
2235 public Response getPublicationList(@PathParam("eventId") String id) throws Exception {
2236 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2237 if (optEvent.isEmpty()) {
2238 return notFound("Cannot find an event with id '%s'.", id);
2239 }
2240 MediaPackage mp = getIndexService().getEventMediapackage(optEvent.get());
2241 return okJson(getEventPublications(mp.getPublications()));
2242 }
2243
2244 @GET
2245 @Path("{eventId}/asset/publication/{id}.json")
2246 @Produces(MediaType.APPLICATION_JSON)
2247 @RestQuery(
2248 name = "getPublication",
2249 description = "Returns the details of a publication from the given event and publication id as JSON",
2250 returnDescription = "The details of a publication from the given event and publication id as JSON",
2251 pathParameters = {
2252 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2253 type = RestParameter.Type.STRING),
2254 @RestParameter(name = "id", description = "The publication id", isRequired = true,
2255 type = RestParameter.Type.STRING)
2256 },
2257 responses = {
2258 @RestResponse(description = "Returns the publication of a catalog from the given event and publication id as "
2259 + "JSON", responseCode = HttpServletResponse.SC_OK),
2260 @RestResponse(description = "No event or publication with this identifier was found.",
2261 responseCode = HttpServletResponse.SC_NOT_FOUND)
2262 })
2263 public Response getPublication(@PathParam("eventId") String eventId, @PathParam("id") String id)
2264 throws NotFoundException, SearchIndexException, IndexServiceException {
2265 MediaPackage mp = getMediaPackageByEventId(eventId);
2266
2267 Publication publication = null;
2268 for (Publication p : mp.getPublications()) {
2269 if (id.equals(p.getIdentifier())) {
2270 publication = p;
2271 break;
2272 }
2273 }
2274
2275 if (publication == null) {
2276 return notFound("Cannot find publication with id '%s'.", id);
2277 }
2278 return okJson(publicationToJSON(publication));
2279 }
2280
2281 @GET
2282 @Path("{eventId}/tobira/pages")
2283 @RestQuery(
2284 name = "getEventHostPages",
2285 description = "Returns the pages of a connected Tobira instance that contain the given event",
2286 returnDescription = "The Tobira pages that contain the given event",
2287 pathParameters = {
2288 @RestParameter(
2289 name = "eventId",
2290 isRequired = true,
2291 description = "The event identifier",
2292 type = STRING
2293 ),
2294 },
2295 responses = {
2296 @RestResponse(
2297 responseCode = SC_OK,
2298 description = "The Tobira pages containing the given event"
2299 ),
2300 @RestResponse(
2301 responseCode = SC_NOT_FOUND,
2302 description = "Tobira doesn't know about the given event"
2303 ),
2304 @RestResponse(
2305 responseCode = SC_SERVICE_UNAVAILABLE,
2306 description = "Tobira is not configured (correctly)"
2307 ),
2308 }
2309 )
2310 public Response getEventHostPages(@PathParam("eventId") String eventId) {
2311 var tobira = TobiraService.getTobira(getSecurityService().getOrganization().getId());
2312 if (!tobira.ready()) {
2313 return Response.status(Status.SERVICE_UNAVAILABLE)
2314 .entity("Tobira is not configured (correctly)")
2315 .build();
2316 }
2317
2318 try {
2319 var eventData = tobira.getEventHostPages(eventId);
2320 if (eventData == null) {
2321 throw new WebApplicationException(NOT_FOUND);
2322 }
2323 eventData.put("baseURL", tobira.getOrigin());
2324 return Response.ok(eventData.toJSONString()).build();
2325 } catch (TobiraException e) {
2326 throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
2327 }
2328 }
2329
2330 @GET
2331 @Path("{eventId}/workflows.json")
2332 @Produces(MediaType.APPLICATION_JSON)
2333 @RestQuery(
2334 name = "geteventworkflows",
2335 description = "Returns all the data related to the workflows tab in the event details modal as JSON",
2336 returnDescription = "All the data related to the event workflows tab as JSON",
2337 pathParameters = {
2338 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2339 type = RestParameter.Type.STRING)
2340 },
2341 responses = {
2342 @RestResponse(description = "Returns all the data related to the event workflows tab as JSON",
2343 responseCode = HttpServletResponse.SC_OK),
2344 @RestResponse(description = "No event with this identifier was found.",
2345 responseCode = HttpServletResponse.SC_NOT_FOUND)
2346 })
2347 public Response getEventWorkflows(@PathParam("eventId") String id)
2348 throws UnauthorizedException, SearchIndexException, JobEndpointException {
2349 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2350 if (optEvent.isEmpty()) {
2351 return notFound("Cannot find an event with id '%s'.", id);
2352 }
2353
2354 try {
2355 if (optEvent.get().getEventStatus().equals("EVENTS.EVENTS.STATUS.SCHEDULED")) {
2356 Map<String, String> workflowConfig = getSchedulerService().getWorkflowConfig(id);
2357 JsonObject configJson = new JsonObject();
2358 for (Map.Entry<String, String> entry : workflowConfig.entrySet()) {
2359 configJson.addProperty(entry.getKey(), safeString(entry.getValue()));
2360 }
2361
2362 Map<String, String> agentConfiguration = getSchedulerService().getCaptureAgentConfiguration(id);
2363 JsonObject responseJson = new JsonObject();
2364 responseJson.addProperty("workflowId", agentConfiguration.getOrDefault(
2365 CaptureParameters.INGEST_WORKFLOW_DEFINITION, ""));
2366 responseJson.add("configuration", configJson);
2367
2368 return okJson(responseJson);
2369 } else {
2370 List<WorkflowInstance> workflowInstances = getWorkflowService().getWorkflowInstancesByMediaPackage(id);
2371 JsonArray jsonArray = new JsonArray();
2372
2373 for (WorkflowInstance instance : workflowInstances) {
2374 JsonObject instanceJson = new JsonObject();
2375 instanceJson.addProperty("id", instance.getId());
2376 instanceJson.addProperty("title", safeString(instance.getTitle()));
2377 instanceJson.addProperty("status", WORKFLOW_STATUS_TRANSLATION_PREFIX + instance.getState().toString());
2378
2379 Date created = instance.getDateCreated();
2380 instanceJson.addProperty("submitted", created != null ? DateTimeSupport.toUTC(created.getTime()) : "");
2381
2382 String submitter = instance.getCreatorName();
2383 instanceJson.addProperty("submitter", safeString(submitter));
2384
2385 User user = submitter == null ? null : getUserDirectoryService().loadUser(submitter);
2386 String submitterName = null;
2387 String submitterEmail = null;
2388 if (user != null) {
2389 submitterName = user.getName();
2390 submitterEmail = user.getEmail();
2391 }
2392 instanceJson.addProperty("submitterName", safeString(submitterName));
2393 instanceJson.addProperty("submitterEmail", safeString(submitterEmail));
2394
2395 jsonArray.add(instanceJson);
2396 }
2397
2398 JsonObject result = new JsonObject();
2399 result.add("results", jsonArray);
2400 result.addProperty("count", workflowInstances.size());
2401
2402 return okJson(result);
2403 }
2404 } catch (NotFoundException e) {
2405 return notFound("Cannot find workflows for event %s", id);
2406 } catch (SchedulerException e) {
2407 logger.error("Unable to get workflow data for event with id {}", id);
2408 throw new WebApplicationException(e, SC_INTERNAL_SERVER_ERROR);
2409 } catch (WorkflowDatabaseException e) {
2410 throw new JobEndpointException(String.format("Not able to get the list of job from the database: %s", e),
2411 e.getCause());
2412 }
2413 }
2414
2415 @PUT
2416 @Path("{eventId}/workflows")
2417 @RestQuery(
2418 name = "updateEventWorkflow",
2419 description = "Update the workflow configuration for the scheduled event with the given id",
2420 pathParameters = {
2421 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2422 type = RestParameter.Type.STRING)
2423 },
2424 restParameters = {
2425 @RestParameter(name = "configuration", isRequired = true, description = "The workflow configuration as JSON",
2426 type = RestParameter.Type.TEXT)
2427 },
2428 responses = {
2429 @RestResponse(description = "Request executed succesfully", responseCode = HttpServletResponse.SC_NO_CONTENT),
2430 @RestResponse(description = "No event with this identifier was found.",
2431 responseCode = HttpServletResponse.SC_NOT_FOUND)
2432 },
2433 returnDescription = "The method does not retrun any content.")
2434 public Response updateEventWorkflow(@PathParam("eventId") String id, @FormParam("configuration") String configuration)
2435 throws SearchIndexException, UnauthorizedException {
2436 Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
2437 if (optEvent.isEmpty()) {
2438 return notFound("Cannot find an event with id '%s'.", id);
2439 }
2440
2441 if (optEvent.get().isScheduledEvent() && !optEvent.get().hasRecordingStarted()) {
2442 try {
2443
2444 JSONObject configJSON;
2445 try {
2446 configJSON = (JSONObject) new JSONParser().parse(configuration);
2447 } catch (Exception e) {
2448 logger.warn("Unable to parse the workflow configuration {}", configuration);
2449 return badRequest();
2450 }
2451
2452 Optional<Map<String, String>> caMetadataOpt = Optional.empty();
2453 Optional<Map<String, String>> workflowConfigOpt = Optional.empty();
2454
2455 String workflowId = (String) configJSON.get("id");
2456 Map<String, String> caMetadata = new HashMap<>(getSchedulerService().getCaptureAgentConfiguration(id));
2457 if (!workflowId.equals(caMetadata.get(CaptureParameters.INGEST_WORKFLOW_DEFINITION))) {
2458 caMetadata.put(CaptureParameters.INGEST_WORKFLOW_DEFINITION, workflowId);
2459 caMetadataOpt = Optional.of(caMetadata);
2460 }
2461
2462 Map<String, String> workflowConfig = new HashMap<>((JSONObject) configJSON.get("configuration"));
2463 Map<String, String> oldWorkflowConfig = new HashMap<>(getSchedulerService().getWorkflowConfig(id));
2464 if (!oldWorkflowConfig.equals(workflowConfig)) {
2465 workflowConfigOpt = Optional.of(workflowConfig);
2466 }
2467
2468 if (caMetadataOpt.isEmpty() && workflowConfigOpt.isEmpty()) {
2469 return Response.noContent().build();
2470 }
2471
2472 checkAgentAccessForAgent(optEvent.get().getAgentId());
2473
2474 getSchedulerService().updateEvent(id, Optional.empty(), Optional.empty(), Optional.empty(),
2475 Optional.empty(), Optional.empty(), workflowConfigOpt, caMetadataOpt);
2476 return Response.noContent().build();
2477 } catch (NotFoundException e) {
2478 return notFound("Cannot find event %s in scheduler service", id);
2479 } catch (SchedulerException e) {
2480 logger.error("Unable to update scheduling workflow data for event with id {}", id);
2481 throw new WebApplicationException(e, SC_INTERNAL_SERVER_ERROR);
2482 }
2483 } else {
2484 return badRequest(String.format("Event %s workflow can not be updated as the recording already started.", id));
2485 }
2486 }
2487
2488 @GET
2489 @Path("{eventId}/workflows/{workflowId}")
2490 @Produces(MediaType.APPLICATION_JSON)
2491 @RestQuery(
2492 name = "geteventworkflow",
2493 description = "Returns all the data related to the single workflow tab in the event details modal as JSON",
2494 returnDescription = "All the data related to the event singe workflow tab as JSON",
2495 pathParameters = {
2496 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2497 type = RestParameter.Type.STRING),
2498 @RestParameter(name = "workflowId", description = "The workflow id", isRequired = true,
2499 type = RestParameter.Type.STRING)
2500 },
2501 responses = {
2502 @RestResponse(description = "Returns all the data related to the event single workflow tab as JSON",
2503 responseCode = HttpServletResponse.SC_OK),
2504 @RestResponse(description = "Unable to parse workflowId", responseCode = HttpServletResponse.SC_BAD_REQUEST),
2505 @RestResponse(description = "No event with this identifier was found.",
2506 responseCode = HttpServletResponse.SC_NOT_FOUND)
2507 })
2508 public Response getEventWorkflow(@PathParam("eventId") String eventId, @PathParam("workflowId") String workflowId)
2509 throws SearchIndexException {
2510 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
2511 if (optEvent.isEmpty()) {
2512 return notFound("Cannot find an event with id '%s'.", eventId);
2513 }
2514
2515 long workflowInstanceId;
2516 try {
2517 workflowId = StringUtils.remove(workflowId, ".json");
2518 workflowInstanceId = Long.parseLong(workflowId);
2519 } catch (Exception e) {
2520 logger.warn("Unable to parse workflow id {}", workflowId);
2521 return RestUtil.R.badRequest();
2522 }
2523
2524 try {
2525 WorkflowInstance instance = getWorkflowService().getWorkflowById(workflowInstanceId);
2526
2527 Date created = instance.getDateCreated();
2528 Date completed = instance.getDateCompleted();
2529 if (completed == null) {
2530 completed = new Date();
2531 }
2532
2533 long executionTime = completed.getTime() - created.getTime();
2534
2535 JsonObject configurationObj = new JsonObject();
2536 for (Entry<String, String> entry : instance.getConfigurations().entrySet()) {
2537 configurationObj.addProperty(entry.getKey(), safeString(entry.getValue()));
2538 }
2539
2540 JsonObject json = new JsonObject();
2541 json.addProperty("status", WORKFLOW_STATUS_TRANSLATION_PREFIX + instance.getState());
2542 json.addProperty("description", safeString(instance.getDescription()));
2543 json.addProperty("executionTime", executionTime);
2544 json.addProperty("wiid", instance.getId());
2545 json.addProperty("title", safeString(instance.getTitle()));
2546 json.addProperty("wdid", safeString(instance.getTemplate()));
2547 if (!configurationObj.isEmpty()) {
2548 json.add("configuration", configurationObj);
2549 }
2550 json.addProperty("submittedAt", DateTimeSupport.toUTC(created.getTime()));
2551 json.addProperty("creator", safeString(instance.getCreatorName()));
2552
2553 return okJson(json);
2554
2555 } catch (NotFoundException e) {
2556 return notFound("Cannot find workflow %s", workflowId);
2557 } catch (WorkflowDatabaseException e) {
2558 logger.error("Unable to get workflow {} of event {}", workflowId, eventId, e);
2559 return serverError();
2560 } catch (UnauthorizedException e) {
2561 return forbidden();
2562 }
2563 }
2564
2565 @GET
2566 @Path("{eventId}/workflows/{workflowId}/operations.json")
2567 @Produces(MediaType.APPLICATION_JSON)
2568 @RestQuery(
2569 name = "geteventoperations",
2570 description = "Returns all the data related to the workflow/operations tab in the event details modal as JSON",
2571 returnDescription = "All the data related to the event workflow/opertations tab as JSON",
2572 pathParameters = {
2573 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2574 type = RestParameter.Type.STRING),
2575 @RestParameter(name = "workflowId", description = "The workflow id", isRequired = true,
2576 type = RestParameter.Type.STRING)
2577 },
2578 responses = {
2579 @RestResponse(description = "Returns all the data related to the event workflow/operations tab as JSON",
2580 responseCode = HttpServletResponse.SC_OK),
2581 @RestResponse(description = "Unable to parse workflowId", responseCode = HttpServletResponse.SC_BAD_REQUEST),
2582 @RestResponse(description = "No event with this identifier was found.",
2583 responseCode = HttpServletResponse.SC_NOT_FOUND)
2584 })
2585 public Response getEventOperations(@PathParam("eventId") String eventId, @PathParam("workflowId") String workflowId)
2586 throws SearchIndexException {
2587 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
2588 if (optEvent.isEmpty()) {
2589 return notFound("Cannot find an event with id '%s'.", eventId);
2590 }
2591
2592 long workflowInstanceId;
2593 try {
2594 workflowInstanceId = Long.parseLong(workflowId);
2595 } catch (Exception e) {
2596 logger.warn("Unable to parse workflow id {}", workflowId);
2597 return RestUtil.R.badRequest();
2598 }
2599
2600 try {
2601 WorkflowInstance instance = getWorkflowService().getWorkflowById(workflowInstanceId);
2602 List<WorkflowOperationInstance> operations = instance.getOperations();
2603 JsonArray operationsJsonArray = new JsonArray();
2604
2605 for (WorkflowOperationInstance wflOp : operations) {
2606 JsonObject operationJson = new JsonObject();
2607 operationJson.addProperty("status", WORKFLOW_STATUS_TRANSLATION_PREFIX + wflOp.getState());
2608 operationJson.addProperty("title", safeString(wflOp.getTemplate()));
2609 operationJson.addProperty("description", safeString(wflOp.getDescription()));
2610 operationJson.addProperty("id", wflOp.getId());
2611 if (!wflOp.getConfigurationKeys().isEmpty()) {
2612 operationJson.add("configuration", collectionToJsonArray(wflOp.getConfigurationKeys()));
2613 }
2614 operationsJsonArray.add(operationJson);
2615 }
2616
2617 return okJson(operationsJsonArray);
2618 } catch (NotFoundException e) {
2619 return notFound("Cannot find workflow %s", workflowId);
2620 } catch (WorkflowDatabaseException e) {
2621 logger.error("Unable to get workflow operations of event {} and workflow {}", eventId, workflowId, e);
2622 return serverError();
2623 } catch (UnauthorizedException e) {
2624 return forbidden();
2625 }
2626 }
2627
2628 @GET
2629 @Path("{eventId}/workflows/{workflowId}/operations/{operationPosition}")
2630 @Produces(MediaType.APPLICATION_JSON)
2631 @RestQuery(
2632 name = "geteventoperation",
2633 description = "Returns all the data related to the workflow/operation tab in the event details modal as JSON",
2634 returnDescription = "All the data related to the event workflow/opertation tab as JSON",
2635 pathParameters = {
2636 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2637 type = RestParameter.Type.STRING),
2638 @RestParameter(name = "workflowId", description = "The workflow id", isRequired = true,
2639 type = RestParameter.Type.STRING),
2640 @RestParameter(name = "operationPosition", description = "The operation position", isRequired = true,
2641 type = RestParameter.Type.INTEGER)
2642 },
2643 responses = {
2644 @RestResponse(description = "Returns all the data related to the event workflow/operation tab as JSON",
2645 responseCode = HttpServletResponse.SC_OK),
2646 @RestResponse(description = "Unable to parse workflowId or operationPosition",
2647 responseCode = HttpServletResponse.SC_BAD_REQUEST),
2648 @RestResponse(description = "No operation with these identifiers was found.",
2649 responseCode = HttpServletResponse.SC_NOT_FOUND)
2650 })
2651 public Response getEventOperation(@PathParam("eventId") String eventId, @PathParam("workflowId") String workflowId,
2652 @PathParam("operationPosition") Integer operationPosition) throws SearchIndexException {
2653 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
2654 if (optEvent.isEmpty()) {
2655 return notFound("Cannot find an event with id '%s'.", eventId);
2656 }
2657
2658 long workflowInstanceId;
2659 try {
2660 workflowInstanceId = Long.parseLong(workflowId);
2661 } catch (Exception e) {
2662 logger.warn("Unable to parse workflow id {}", workflowId);
2663 return RestUtil.R.badRequest();
2664 }
2665
2666 WorkflowInstance instance;
2667 try {
2668 instance = getWorkflowService().getWorkflowById(workflowInstanceId);
2669 } catch (NotFoundException e) {
2670 return notFound("Cannot find workflow %s", workflowId);
2671 } catch (WorkflowDatabaseException e) {
2672 logger.error("Unable to get workflow operation of event {} and workflow {} at position {}", eventId, workflowId,
2673 operationPosition, e);
2674 return serverError();
2675 } catch (UnauthorizedException e) {
2676 return forbidden();
2677 }
2678
2679 List<WorkflowOperationInstance> operations = instance.getOperations();
2680
2681 if (operationPosition < operations.size()) {
2682 WorkflowOperationInstance wflOp = operations.get(operationPosition);
2683 JsonObject json = new JsonObject();
2684
2685 json.addProperty("retry_strategy", wflOp.getRetryStrategy() != null ? wflOp.getRetryStrategy().toString() : "");
2686 json.addProperty("execution_host", safeString(wflOp.getExecutionHost()));
2687 json.addProperty("failed_attempts", wflOp.getFailedAttempts());
2688 json.addProperty("max_attempts", wflOp.getMaxAttempts());
2689 json.addProperty("exception_handler_workflow", safeString(wflOp.getExceptionHandlingWorkflow()));
2690 json.addProperty("fail_on_error", wflOp.isFailOnError());
2691 json.addProperty("description", safeString(wflOp.getDescription()));
2692 json.addProperty("state", WORKFLOW_STATUS_TRANSLATION_PREFIX + wflOp.getState());
2693 json.addProperty("job", wflOp.getId());
2694 json.addProperty("name", safeString(wflOp.getTemplate()));
2695 json.addProperty("time_in_queue", wflOp.getTimeInQueue() != null ? wflOp.getTimeInQueue() : 0);
2696 json.addProperty("started", wflOp.getDateStarted() != null ? toUTC(wflOp.getDateStarted().getTime()) : "");
2697 json.addProperty("completed", wflOp.getDateCompleted() != null ? toUTC(wflOp.getDateCompleted().getTime()) : "");
2698
2699 return okJson(json);
2700 }
2701
2702 return notFound("Cannot find workflow operation of workflow %s at position %s", workflowId, operationPosition);
2703 }
2704
2705 @GET
2706 @Path("{eventId}/workflows/{workflowId}/errors.json")
2707 @Produces(MediaType.APPLICATION_JSON)
2708 @RestQuery(
2709 name = "geteventerrors",
2710 description = "Returns all the data related to the workflow/errors tab in the event details modal as JSON",
2711 returnDescription = "All the data related to the event workflow/errors tab as JSON",
2712 pathParameters = {
2713 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2714 type = RestParameter.Type.STRING),
2715 @RestParameter(name = "workflowId", description = "The workflow id", isRequired = true,
2716 type = RestParameter.Type.STRING)
2717 },
2718 responses = {
2719 @RestResponse(description = "Returns all the data related to the event workflow/errors tab as JSON",
2720 responseCode = HttpServletResponse.SC_OK),
2721 @RestResponse(description = "Unable to parse workflowId", responseCode = HttpServletResponse.SC_BAD_REQUEST),
2722 @RestResponse(description = "No event with this identifier was found.",
2723 responseCode = HttpServletResponse.SC_NOT_FOUND)
2724 })
2725 public Response getEventErrors(@PathParam("eventId") String eventId, @PathParam("workflowId") String workflowId,
2726 @Context HttpServletRequest req) throws JobEndpointException, SearchIndexException {
2727
2728
2729
2730
2731 Optional<Event> eventOpt = getIndexService().getEvent(eventId, getIndex());
2732 if (eventOpt.isPresent()) {
2733 final long workflowIdLong;
2734 try {
2735 workflowIdLong = Long.parseLong(workflowId);
2736 } catch (Exception e) {
2737 logger.warn("Unable to parse workflow id {}", workflowId);
2738 return RestUtil.R.badRequest();
2739 }
2740 try {
2741 return okJson(getJobService().getIncidentsAsJSON(workflowIdLong, req.getLocale(), true));
2742 } catch (NotFoundException e) {
2743 return notFound("Cannot find the incident for the workflow %s", workflowId);
2744 }
2745 }
2746 return notFound("Cannot find an event with id '%s'.", eventId);
2747 }
2748
2749 @GET
2750 @Path("{eventId}/workflows/{workflowId}/errors/{errorId}.json")
2751 @Produces(MediaType.APPLICATION_JSON)
2752 @RestQuery(
2753 name = "geteventerror",
2754 description = "Returns all the data related to the workflow/error tab in the event details modal as JSON",
2755 returnDescription = "All the data related to the event workflow/error tab as JSON",
2756 pathParameters = {
2757 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2758 type = RestParameter.Type.STRING),
2759 @RestParameter(name = "workflowId", description = "The workflow id", isRequired = true,
2760 type = RestParameter.Type.STRING),
2761 @RestParameter(name = "errorId", description = "The error id", isRequired = true,
2762 type = RestParameter.Type.STRING)
2763 },
2764 responses = {
2765 @RestResponse(description = "Returns all the data related to the event workflow/error tab as JSON",
2766 responseCode = HttpServletResponse.SC_OK),
2767 @RestResponse(description = "Unable to parse workflowId", responseCode = HttpServletResponse.SC_BAD_REQUEST),
2768 @RestResponse(description = "No event with this identifier was found.",
2769 responseCode = HttpServletResponse.SC_NOT_FOUND)
2770 })
2771
2772 public Response getEventError(@PathParam("eventId") String eventId, @PathParam("workflowId") String workflowId,
2773 @PathParam("errorId") String errorId, @Context HttpServletRequest req)
2774 throws JobEndpointException, SearchIndexException {
2775
2776
2777
2778
2779 Optional<Event> eventOpt = getIndexService().getEvent(eventId, getIndex());
2780 if (eventOpt.isPresent()) {
2781 final long errorIdLong;
2782 try {
2783 errorIdLong = Long.parseLong(errorId);
2784 } catch (Exception e) {
2785 logger.warn("Unable to parse error id {}", errorId);
2786 return RestUtil.R.badRequest();
2787 }
2788 try {
2789 return okJson(getJobService().getIncidentAsJSON(errorIdLong, req.getLocale()));
2790 } catch (NotFoundException e) {
2791 return notFound("Cannot find the incident %s", errorId);
2792 }
2793 }
2794 return notFound("Cannot find an event with id '%s'.", eventId);
2795 }
2796
2797 @GET
2798 @Path("{eventId}/access.json")
2799 @SuppressWarnings("unchecked")
2800 @Produces(MediaType.APPLICATION_JSON)
2801 @RestQuery(
2802 name = "getEventAccessInformation",
2803 description = "Get the access information of an event",
2804 returnDescription = "The access information",
2805 pathParameters = {
2806 @RestParameter(name = "eventId", isRequired = true, description = "The event identifier",
2807 type = RestParameter.Type.STRING)
2808 },
2809 responses = {
2810 @RestResponse(responseCode = SC_BAD_REQUEST, description = "The required form params were missing in the "
2811 + "request."),
2812 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the event has not been found."),
2813 @RestResponse(responseCode = SC_OK, description = "The access information ")
2814 })
2815 public Response getEventAccessInformation(@PathParam("eventId") String eventId) throws Exception {
2816 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
2817 if (optEvent.isEmpty()) {
2818 return notFound("Cannot find an event with id '%s'.", eventId);
2819 }
2820
2821
2822 JSONArray systemAclsJson = new JSONArray();
2823 List<ManagedAcl> acls = getAclService().getAcls();
2824 for (ManagedAcl acl : acls) {
2825 systemAclsJson.add(AccessInformationUtil.serializeManagedAcl(acl));
2826 }
2827
2828 AccessControlList activeAcl = new AccessControlList();
2829 try {
2830 if (optEvent.get().getAccessPolicy() != null) {
2831 activeAcl = AccessControlParser.parseAcl(optEvent.get().getAccessPolicy());
2832 }
2833 } catch (Exception e) {
2834 logger.error("Unable to parse access policy", e);
2835 }
2836 Optional<ManagedAcl> currentAcl = AccessInformationUtil.matchAclsLenient(acls, activeAcl,
2837 getAdminUIConfiguration().getMatchManagedAclRolePrefixes());
2838
2839 JSONObject episodeAccessJson = new JSONObject();
2840 episodeAccessJson.put("current_acl", currentAcl.isPresent() ? currentAcl.get().getId() : 0L);
2841 episodeAccessJson.put("acl", transformAccessControList(activeAcl, getUserDirectoryService()));
2842 episodeAccessJson.put("privileges", AccessInformationUtil.serializePrivilegesByRole(activeAcl));
2843 if (StringUtils.isNotBlank(optEvent.get().getWorkflowState())
2844 && WorkflowUtil.isActive(WorkflowInstance.WorkflowState.valueOf(optEvent.get().getWorkflowState()))) {
2845 episodeAccessJson.put("locked", true);
2846 }
2847
2848 JSONObject jsonReturnObj = new JSONObject();
2849 jsonReturnObj.put("episode_access", episodeAccessJson);
2850 jsonReturnObj.put("system_acls", systemAclsJson);
2851
2852 return Response.ok(jsonReturnObj.toString()).build();
2853 }
2854
2855
2856 @POST
2857 @Path("{eventId}/assets")
2858 @Consumes(MediaType.MULTIPART_FORM_DATA)
2859 @RestQuery(
2860 name = "updateAssets",
2861 description = "Update or create an asset for the eventId by the given metadata as JSON and files in the body",
2862 pathParameters = {
2863 @RestParameter(name = "eventId", description = "The event id", isRequired = true,
2864 type = RestParameter.Type.STRING)
2865 },
2866 restParameters = {
2867 @RestParameter(name = "metadata", isRequired = true, type = RestParameter.Type.TEXT,
2868 description = "The list of asset metadata")
2869 },
2870 responses = {
2871 @RestResponse(description = "The asset has been added.", responseCode = HttpServletResponse.SC_OK),
2872 @RestResponse(description = "Could not add asset, problem with the metadata or files.",
2873 responseCode = HttpServletResponse.SC_BAD_REQUEST),
2874 @RestResponse(description = "No event with this identifier was found.",
2875 responseCode = HttpServletResponse.SC_NOT_FOUND)
2876 },
2877 returnDescription = "The workflow identifier")
2878 public Response updateAssets(@PathParam("eventId") final String eventId,
2879 @Context HttpServletRequest request) throws Exception {
2880 try {
2881 MediaPackage mp = getMediaPackageByEventId(eventId);
2882 String result = getIndexService().updateEventAssets(mp, request);
2883 return Response.status(Status.CREATED).entity(result).build();
2884 } catch (NotFoundException e) {
2885 return notFound("Cannot find an event with id '%s'.", eventId);
2886 } catch (IllegalArgumentException | UnsupportedAssetException e) {
2887 return RestUtil.R.badRequest(e.getMessage());
2888 } catch (Exception e) {
2889 return RestUtil.R.serverError();
2890 }
2891 }
2892
2893 @GET
2894 @Path("new/metadata")
2895 @RestQuery(
2896 name = "getNewMetadata",
2897 description = "Returns all the data related to the metadata tab in the new event modal as JSON",
2898 returnDescription = "All the data related to the event metadata tab as JSON",
2899 responses = {
2900 @RestResponse(responseCode = SC_OK, description = "Returns all the data related to the event metadata tab as "
2901 + "JSON")
2902 })
2903 public Response getNewMetadata() {
2904 MetadataList metadataList = new MetadataList();
2905
2906
2907 List<EventCatalogUIAdapter> extendedCatalogUIAdapters = getIndexService().getExtendedEventCatalogUIAdapters();
2908 for (EventCatalogUIAdapter extendedCatalogUIAdapter : extendedCatalogUIAdapters) {
2909 metadataList.add(extendedCatalogUIAdapter, extendedCatalogUIAdapter.getRawFields());
2910 }
2911
2912
2913
2914
2915 EventCatalogUIAdapter commonCatalogUiAdapter = getIndexService().getCommonEventCatalogUIAdapter();
2916 DublinCoreMetadataCollection commonMetadata = commonCatalogUiAdapter.getRawFields(getCollectionQueryDisable());
2917
2918 if (commonMetadata.getOutputFields().containsKey(DublinCore.PROPERTY_CREATED.getLocalName())) {
2919 commonMetadata.removeField(commonMetadata.getOutputFields().get(DublinCore.PROPERTY_CREATED.getLocalName()));
2920 }
2921 if (commonMetadata.getOutputFields().containsKey("duration")) {
2922 commonMetadata.removeField(commonMetadata.getOutputFields().get("duration"));
2923 }
2924 if (commonMetadata.getOutputFields().containsKey(DublinCore.PROPERTY_IDENTIFIER.getLocalName())) {
2925 commonMetadata.removeField(commonMetadata.getOutputFields().get(DublinCore.PROPERTY_IDENTIFIER.getLocalName()));
2926 }
2927 if (commonMetadata.getOutputFields().containsKey(DublinCore.PROPERTY_SOURCE.getLocalName())) {
2928 commonMetadata.removeField(commonMetadata.getOutputFields().get(DublinCore.PROPERTY_SOURCE.getLocalName()));
2929 }
2930 if (commonMetadata.getOutputFields().containsKey("startDate")) {
2931 commonMetadata.removeField(commonMetadata.getOutputFields().get("startDate"));
2932 }
2933 if (commonMetadata.getOutputFields().containsKey("startTime")) {
2934 commonMetadata.removeField(commonMetadata.getOutputFields().get("startTime"));
2935 }
2936 if (commonMetadata.getOutputFields().containsKey("location")) {
2937 commonMetadata.removeField(commonMetadata.getOutputFields().get("location"));
2938 }
2939
2940
2941 if (commonMetadata.getOutputFields().containsKey(DublinCore.PROPERTY_PUBLISHER.getLocalName())) {
2942 MetadataField publisher = commonMetadata.getOutputFields().get(DublinCore.PROPERTY_PUBLISHER.getLocalName());
2943 Map<String, String> users = new HashMap<>();
2944 if (publisher.getCollection() != null) {
2945 users = publisher.getCollection();
2946 }
2947 String loggedInUser = getSecurityService().getUser().getName();
2948 if (!users.containsKey(loggedInUser)) {
2949 users.put(loggedInUser, loggedInUser);
2950 }
2951 publisher.setValue(loggedInUser);
2952 }
2953
2954 metadataList.add(commonCatalogUiAdapter, commonMetadata);
2955
2956
2957 removeSeriesWithNullTitlesFromFieldCollection(metadataList);
2958
2959 return okJson(MetadataJson.listToJson(metadataList, true));
2960 }
2961
2962 @GET
2963 @Path("new/processing")
2964 @RestQuery(
2965 name = "getNewProcessing",
2966 description = "Returns all the data related to the processing tab in the new event modal as JSON",
2967 returnDescription = "All the data related to the event processing tab as JSON",
2968 restParameters = {
2969 @RestParameter(name = "tags", isRequired = false, description = "A comma separated list of tags to filter "
2970 + "the workflow definitions", type = RestParameter.Type.STRING)
2971 },
2972 responses = {
2973 @RestResponse(responseCode = SC_OK, description = "Returns all the data related to the event processing tab "
2974 + "as JSON")
2975 })
2976 public Response getNewProcessing(@QueryParam("tags") String tagsString) {
2977 List<String> tags = RestUtil.splitCommaSeparatedParam(Optional.ofNullable(tagsString));
2978
2979 JsonArray workflowsArray = new JsonArray();
2980 try {
2981 List<WorkflowDefinition> workflowsDefinitions = getWorkflowService().listAvailableWorkflowDefinitions();
2982 for (WorkflowDefinition wflDef : workflowsDefinitions) {
2983 if (wflDef.containsTag(tags)) {
2984 JsonObject wfJson = new JsonObject();
2985 wfJson.addProperty("id", wflDef.getId());
2986 wfJson.add("tags", arrayToJsonArray(wflDef.getTags()));
2987 wfJson.addProperty("title", safeString(wflDef.getTitle()));
2988 wfJson.addProperty("description", safeString(wflDef.getDescription()));
2989 wfJson.addProperty("displayOrder", wflDef.getDisplayOrder());
2990 wfJson.addProperty("configuration_panel", safeString(wflDef.getConfigurationPanel()));
2991 wfJson.addProperty("configuration_panel_json", safeString(wflDef.getConfigurationPanelJson()));
2992
2993 workflowsArray.add(wfJson);
2994 }
2995 }
2996 } catch (WorkflowDatabaseException e) {
2997 logger.error("Unable to get available workflow definitions", e);
2998 return RestUtil.R.serverError();
2999 }
3000
3001 JsonObject data = new JsonObject();
3002 data.add("workflows", workflowsArray);
3003 data.addProperty("default_workflow_id", defaultWorkflowDefinionId);
3004
3005 return okJson(data);
3006 }
3007
3008 @POST
3009 @Path("new/conflicts")
3010 @RestQuery(
3011 name = "checkNewConflicts",
3012 description = "Checks if the current scheduler parameters are in a conflict with another event",
3013 returnDescription = "Returns NO CONTENT if no event are in conflict within specified period or list of "
3014 + "conflicting recordings in JSON",
3015 restParameters = {
3016 @RestParameter(name = "metadata", isRequired = true, description = "The metadata as JSON",
3017 type = RestParameter.Type.TEXT)
3018 },
3019 responses = {
3020 @RestResponse(responseCode = HttpServletResponse.SC_NO_CONTENT, description = "No conflicting events found"),
3021 @RestResponse(responseCode = HttpServletResponse.SC_CONFLICT, description = "There is a conflict"),
3022 @RestResponse(responseCode = HttpServletResponse.SC_BAD_REQUEST, description = "Missing or invalid "
3023 + "parameters")
3024 })
3025 public Response getNewConflicts(@FormParam("metadata") String metadata) throws NotFoundException {
3026 if (StringUtils.isBlank(metadata)) {
3027 logger.warn("Metadata is not specified");
3028 return Response.status(Status.BAD_REQUEST).build();
3029 }
3030
3031 JSONParser parser = new JSONParser();
3032 JSONObject metadataJson;
3033 try {
3034 metadataJson = (JSONObject) parser.parse(metadata);
3035 } catch (Exception e) {
3036 logger.warn("Unable to parse metadata {}", metadata);
3037 return RestUtil.R.badRequest("Unable to parse metadata");
3038 }
3039
3040 String device;
3041 String startDate;
3042 String endDate;
3043 try {
3044 device = (String) metadataJson.get("device");
3045 startDate = (String) metadataJson.get("start");
3046 endDate = (String) metadataJson.get("end");
3047 } catch (Exception e) {
3048 logger.warn("Unable to parse metadata {}", metadata);
3049 return RestUtil.R.badRequest("Unable to parse metadata");
3050 }
3051
3052 if (StringUtils.isBlank(device) || StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
3053 logger.warn("Either device, start date or end date were not specified");
3054 return Response.status(Status.BAD_REQUEST).build();
3055 }
3056
3057 Date start;
3058 try {
3059 start = new Date(DateTimeSupport.fromUTC(startDate));
3060 } catch (Exception e) {
3061 logger.warn("Unable to parse start date {}", startDate);
3062 return RestUtil.R.badRequest("Unable to parse start date");
3063 }
3064
3065 Date end;
3066 try {
3067 end = new Date(DateTimeSupport.fromUTC(endDate));
3068 } catch (Exception e) {
3069 logger.warn("Unable to parse end date {}", endDate);
3070 return RestUtil.R.badRequest("Unable to parse end date");
3071 }
3072
3073 String rruleString = (String) metadataJson.get("rrule");
3074
3075 RRule rrule = null;
3076 TimeZone timeZone = TimeZone.getDefault();
3077 String durationString = null;
3078 if (StringUtils.isNotEmpty(rruleString)) {
3079 try {
3080 rrule = new RRule(rruleString);
3081 rrule.validate();
3082 } catch (Exception e) {
3083 logger.warn("Unable to parse rrule {}: {}", rruleString, e.getMessage());
3084 return Response.status(Status.BAD_REQUEST).build();
3085 }
3086
3087 durationString = (String) metadataJson.get("duration");
3088 if (StringUtils.isBlank(durationString)) {
3089 logger.warn("If checking recurrence, must include duration.");
3090 return Response.status(Status.BAD_REQUEST).build();
3091 }
3092
3093 Agent agent = getCaptureAgentStateService().getAgent(device);
3094 String timezone = agent.getConfiguration().getProperty("capture.device.timezone");
3095 if (StringUtils.isBlank(timezone)) {
3096 timezone = TimeZone.getDefault().getID();
3097 logger.warn("No 'capture.device.timezone' set on agent {}. The default server timezone {} will be used.",
3098 device, timezone);
3099 }
3100 timeZone = TimeZone.getTimeZone(timezone);
3101 }
3102
3103 String eventId = (String) metadataJson.get("id");
3104
3105 try {
3106 List<MediaPackage> events = null;
3107 if (StringUtils.isNotEmpty(rruleString)) {
3108 events = getSchedulerService().findConflictingEvents(device, rrule, start, end, Long.parseLong(durationString),
3109 timeZone);
3110 } else {
3111 events = getSchedulerService().findConflictingEvents(device, start, end);
3112 }
3113 if (!events.isEmpty()) {
3114 final List<JsonObject> eventsJSON = convertToConflictObjects(eventId, events);
3115 if (!eventsJSON.isEmpty()) {
3116 JsonArray jsonArray = new JsonArray();
3117 for (JsonObject jsonObj : eventsJSON) {
3118 jsonArray.add(jsonObj);
3119 }
3120 return conflictJson(jsonArray);
3121 }
3122 }
3123 return Response.noContent().build();
3124 } catch (Exception e) {
3125 logger.error("Unable to find conflicting events for {}, {}, {}",
3126 device, startDate, endDate, e);
3127 return RestUtil.R.serverError();
3128 }
3129 }
3130
3131 private List<JsonObject> convertToConflictObjects(final String eventId, final List<MediaPackage> events)
3132 throws SearchIndexException {
3133 final List<JsonObject> eventsJSON = new ArrayList<>();
3134 final Organization organization = getSecurityService().getOrganization();
3135 final User user = SecurityUtil.createSystemUser(systemUserName, organization);
3136
3137 SecurityUtil.runAs(getSecurityService(), organization, user, () -> {
3138 try {
3139 for (final MediaPackage event : events) {
3140 final Optional<Event> eventOpt = getIndexService().getEvent(event.getIdentifier().toString(), getIndex());
3141 if (eventOpt.isPresent()) {
3142 final Event e = eventOpt.get();
3143 if (StringUtils.isNotEmpty(eventId) && eventId.equals(e.getIdentifier())) {
3144 continue;
3145 }
3146 eventsJSON.add(convertEventToConflictingObject(e.getTechnicalStartTime(), e.getTechnicalEndTime(),
3147 e.getTitle()));
3148 } else {
3149 logger.warn("Index out of sync! Conflicting event catalog {} not found on event index!",
3150 event.getIdentifier().toString());
3151 }
3152 }
3153 } catch (Exception e) {
3154 logger.error("Failed to get conflicting events", e);
3155 }
3156 });
3157
3158 return eventsJSON;
3159 }
3160
3161 private JsonObject convertEventToConflictingObject(final String start, final String end, final String title) {
3162 JsonObject json = new JsonObject();
3163 json.addProperty("start", start);
3164 json.addProperty("end", end);
3165 json.addProperty("title", title);
3166 return json;
3167 }
3168
3169 @POST
3170 @Path("/new")
3171 @Consumes(MediaType.MULTIPART_FORM_DATA)
3172 @RestQuery(
3173 name = "createNewEvent",
3174 description = "Creates a new event by the given metadata as JSON and the files in the body",
3175 returnDescription = "The workflow identifier",
3176 restParameters = {
3177 @RestParameter(name = "metadata", isRequired = true, description = "The metadata as JSON",
3178 type = RestParameter.Type.TEXT)
3179 },
3180 responses = {
3181 @RestResponse(responseCode = HttpServletResponse.SC_CREATED, description = "Event sucessfully added"),
3182 @RestResponse(responseCode = SC_BAD_REQUEST, description = "If the metadata is not set or couldn't be parsed")
3183 })
3184 public Response createNewEvent(@Context HttpServletRequest request) {
3185 try {
3186 String result = getIndexService().createEvent(request);
3187 if (StringUtils.isEmpty(result)) {
3188 return RestUtil.R.badRequest("The date range provided did not include any events");
3189 }
3190 return Response.status(Status.CREATED).entity(result).build();
3191 } catch (IllegalArgumentException | UnsupportedAssetException e) {
3192 return RestUtil.R.badRequest(e.getMessage());
3193 } catch (Exception e) {
3194 return RestUtil.R.serverError();
3195 }
3196 }
3197
3198 @GET
3199 @Path("events.json")
3200 @Produces(MediaType.APPLICATION_JSON)
3201 @RestQuery(
3202 name = "getevents",
3203 description = "Returns all the events as JSON",
3204 returnDescription = "All the events as JSON",
3205 restParameters = {
3206 @RestParameter(name = "filter", isRequired = false, description = "The filter used for the query. They "
3207 + "should be formated like that: 'filter1:value1,filter2:value2'", type = STRING),
3208 @RestParameter(name = "sort", description = "The order instructions used to sort the query result. Must be "
3209 + "in the form '<field name>:(ASC|DESC)'", isRequired = false, type = STRING),
3210 @RestParameter(name = "limit", description = "The maximum number of items to return per page.",
3211 isRequired = false, type = RestParameter.Type.INTEGER),
3212 @RestParameter(name = "offset", description = "The page number.", isRequired = false,
3213 type = RestParameter.Type.INTEGER),
3214 @RestParameter(name = "getComments", description = "If comments should be fetched", isRequired = false,
3215 type = RestParameter.Type.BOOLEAN)
3216 },
3217 responses = {
3218 @RestResponse(description = "Returns all events as JSON", responseCode = HttpServletResponse.SC_OK)
3219 })
3220 public Response getEvents(@QueryParam("id") String id, @QueryParam("commentReason") String reasonFilter,
3221 @QueryParam("commentResolution") String resolutionFilter, @QueryParam("filter") String filter,
3222 @QueryParam("sort") String sort, @QueryParam("offset") Integer offset, @QueryParam("limit") Integer limit,
3223 @QueryParam("getComments") Boolean getComments) {
3224
3225 Optional<Integer> optLimit = Optional.ofNullable(limit);
3226 Optional<Integer> optOffset = Optional.ofNullable(offset);
3227 Optional<String> optSort = Optional.ofNullable(trimToNull(sort));
3228 Optional<Boolean> optGetComments = Optional.ofNullable(getComments);
3229 List<JsonObject> eventsList = new ArrayList<>();
3230 final Organization organization = getSecurityService().getOrganization();
3231 final User user = getSecurityService().getUser();
3232 if (organization == null || user == null) {
3233 return Response.status(SC_SERVICE_UNAVAILABLE).build();
3234 }
3235 EventSearchQuery query = new EventSearchQuery(organization.getId(), user);
3236
3237
3238 if (optLimit.isPresent() && limit == 0) {
3239 optLimit = Optional.empty();
3240 }
3241
3242 Map<String, String> filters = RestUtils.parseFilter(filter);
3243 for (String name : filters.keySet()) {
3244 if (EventListQuery.FILTER_PRESENTERS_BIBLIOGRAPHIC_NAME.equals(name)) {
3245 query.withPresenter(filters.get(name));
3246 }
3247 if (EventListQuery.FILTER_PRESENTERS_TECHNICAL_NAME.equals(name)) {
3248 query.withTechnicalPresenters(filters.get(name));
3249 }
3250 if (EventListQuery.FILTER_CONTRIBUTORS_NAME.equals(name)) {
3251 query.withContributor(filters.get(name));
3252 }
3253 if (EventListQuery.FILTER_LOCATION_NAME.equals(name)) {
3254 query.withLocation(filters.get(name));
3255 }
3256 if (EventListQuery.FILTER_LANGUAGE_NAME.equals(name)) {
3257 query.withLanguage(filters.get(name));
3258 }
3259 if (EventListQuery.FILTER_AGENT_NAME.equals(name)) {
3260 query.withAgentId(filters.get(name));
3261 }
3262 if (EventListQuery.FILTER_TEXT_NAME.equals(name)) {
3263 query.withText(filters.get(name));
3264 }
3265 if (EventListQuery.FILTER_SERIES_NAME.equals(name)) {
3266 query.withSeriesId(filters.get(name));
3267 }
3268 if (EventListQuery.FILTER_STATUS_NAME.equals(name)) {
3269 query.withEventStatus(filters.get(name));
3270 }
3271 if (EventListQuery.FILTER_PUBLISHER_NAME.equals(name)) {
3272 query.withPublisher(filters.get(name));
3273 }
3274 if (EventListQuery.FILTER_COMMENTS_NAME.equals(name)) {
3275 switch (Comments.valueOf(filters.get(name))) {
3276 case NONE:
3277 query.withComments(false);
3278 break;
3279 case OPEN:
3280 query.withOpenComments(true);
3281 break;
3282 case RESOLVED:
3283 query.withComments(true);
3284 query.withOpenComments(false);
3285 break;
3286 default:
3287 logger.info("Unknown comment {}", filters.get(name));
3288 return Response.status(SC_BAD_REQUEST).build();
3289 }
3290 }
3291 if (EventListQuery.FILTER_IS_PUBLISHED_NAME.equals(name)) {
3292 if (filters.containsKey(name)) {
3293 switch (IsPublished.valueOf(filters.get(name))) {
3294 case YES:
3295 query.withIsPublished(true);
3296 break;
3297 case NO:
3298 query.withIsPublished(false);
3299 break;
3300 default:
3301 break;
3302 }
3303 } else {
3304 logger.info("Query for invalid published status: {}", filters.get(name));
3305 return Response.status(SC_BAD_REQUEST).build();
3306 }
3307 }
3308 if (EventListQuery.FILTER_STARTDATE_NAME.equals(name)) {
3309 try {
3310 Tuple<Date, Date> fromAndToCreationRange = RestUtils.getFromAndToDateRange(filters.get(name));
3311 query.withStartFrom(fromAndToCreationRange.getA());
3312 query.withStartTo(fromAndToCreationRange.getB());
3313 } catch (IllegalArgumentException e) {
3314 return RestUtil.R.badRequest(e.getMessage());
3315 }
3316 }
3317 }
3318
3319 if (optSort.isPresent()) {
3320 ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(optSort.get());
3321 for (SortCriterion criterion : sortCriteria) {
3322 switch (criterion.getFieldName()) {
3323 case EventIndexSchema.UID:
3324 query.sortByUID(criterion.getOrder());
3325 break;
3326 case EventIndexSchema.TITLE:
3327 query.sortByTitle(criterion.getOrder());
3328 break;
3329 case EventIndexSchema.PRESENTER:
3330 query.sortByPresenter(criterion.getOrder());
3331 break;
3332 case EventIndexSchema.TECHNICAL_START:
3333 case "technical_date":
3334 query.sortByTechnicalStartDate(criterion.getOrder());
3335 break;
3336 case EventIndexSchema.TECHNICAL_END:
3337 query.sortByTechnicalEndDate(criterion.getOrder());
3338 break;
3339 case EventIndexSchema.PUBLICATION:
3340 query.sortByPublicationIgnoringInternal(criterion.getOrder());
3341 break;
3342 case EventIndexSchema.START_DATE:
3343 case "date":
3344 query.sortByStartDate(criterion.getOrder());
3345 break;
3346 case EventIndexSchema.END_DATE:
3347 query.sortByEndDate(criterion.getOrder());
3348 break;
3349 case EventIndexSchema.SERIES_NAME:
3350 query.sortBySeriesName(criterion.getOrder());
3351 break;
3352 case EventIndexSchema.LOCATION:
3353 query.sortByLocation(criterion.getOrder());
3354 break;
3355 case EventIndexSchema.EVENT_STATUS:
3356 query.sortByEventStatus(criterion.getOrder());
3357 break;
3358 default:
3359 final String msg = String.format("Unknown sort criteria field %s", criterion.getFieldName());
3360 logger.debug(msg);
3361 return RestUtil.R.badRequest(msg);
3362 }
3363 }
3364 }
3365
3366
3367 if (getOnlyEventsWithWriteAccessEventsTab()) {
3368 query.withoutActions();
3369 query.withAction(Permissions.Action.WRITE);
3370 query.withAction(Permissions.Action.READ);
3371 }
3372
3373 if (optLimit.isPresent()) {
3374 query.withLimit(optLimit.get());
3375 }
3376 if (optOffset.isPresent()) {
3377 query.withOffset(offset);
3378 }
3379
3380
3381 SearchResult<Event> results = null;
3382 try {
3383 results = getIndex().getByQuery(query);
3384 } catch (SearchIndexException e) {
3385 logger.error("The admin UI Search Index was not able to get the events list:", e);
3386 return RestUtil.R.serverError();
3387 }
3388
3389
3390 if (results.getPageSize() == 0) {
3391 logger.debug("No events match the given filters.");
3392 return okJsonList(eventsList, Optional.ofNullable(offset).orElse(0), Optional.ofNullable(limit).orElse(0), 0);
3393 }
3394
3395 Map<String, String> languages;
3396 try {
3397 languages = getListProvidersService().getList("LANGUAGES", new ResourceListQueryImpl(), false);
3398 } catch (ListProviderException e) {
3399 logger.info("Could not get languages from listprovider");
3400 throw new WebApplicationException(e);
3401 }
3402
3403 for (SearchResultItem<Event> item : results.getItems()) {
3404 Event source = item.getSource();
3405 source.updatePreview(getAdminUIConfiguration().getPreviewSubtype());
3406 List<EventComment> comments = null;
3407 if (optGetComments.isPresent() && optGetComments.get()) {
3408 try {
3409 comments = getEventCommentService().getComments(source.getIdentifier());
3410 } catch (EventCommentException e) {
3411 logger.error("Unable to get comments from event {}", source.getIdentifier(), e);
3412 throw new WebApplicationException(e);
3413 }
3414 }
3415 eventsList.add(eventToJSON(source, Optional.ofNullable(comments), Optional.ofNullable(languages)));
3416 }
3417
3418 return okJsonList(eventsList, Optional.ofNullable(offset).orElse(0), Optional.ofNullable(limit).orElse(0),
3419 results.getHitCount());
3420 }
3421
3422
3423
3424 private MediaPackage getMediaPackageByEventId(String eventId)
3425 throws SearchIndexException, NotFoundException, IndexServiceException {
3426 Optional<Event> optEvent = getIndexService().getEvent(eventId, getIndex());
3427 if (optEvent.isEmpty()) {
3428 throw new NotFoundException(format("Cannot find an event with id '%s'.", eventId));
3429 }
3430 return getIndexService().getEventMediapackage(optEvent.get());
3431 }
3432
3433 private URI getCommentUrl(String eventId, long commentId) {
3434 return UrlSupport.uri(serverUrl, eventId, "comment", Long.toString(commentId));
3435 }
3436
3437
3438 private JsonObject eventToJSON(Event event, Optional<List<EventComment>> comments,
3439 Optional<Map<String, String>> languages) {
3440 JsonObject json = new JsonObject();
3441
3442 json.addProperty("id", event.getIdentifier());
3443 json.addProperty("title", event.getTitle() != null ? event.getTitle() : "");
3444 json.addProperty("source", event.getSource() != null ? event.getSource() : "");
3445 json.add("presenters", collectionToJsonArray(event.getPresenters()));
3446
3447 if (StringUtils.isNotBlank(event.getSeriesId())) {
3448 JsonObject seriesObj = new JsonObject();
3449 seriesObj.addProperty("id", event.getSeriesId() != null ? event.getSeriesId() : "");
3450 seriesObj.addProperty("title", event.getSeriesName() != null ? event.getSeriesName() : "");
3451 json.add("series", seriesObj);
3452 }
3453
3454 json.addProperty("location", safeString(event.getLocation()));
3455 json.addProperty("start_date", safeString(event.getRecordingStartDate()));
3456 json.addProperty("end_date", safeString(event.getRecordingEndDate()));
3457 json.addProperty("managedAcl", safeString(event.getManagedAcl()));
3458 json.addProperty("workflow_state", safeString(event.getWorkflowState()));
3459 json.addProperty("event_status", event.getEventStatus());
3460 json.addProperty("displayable_status", event.getDisplayableStatus(getWorkflowService().getWorkflowStateMappings()));
3461 json.addProperty("source", getIndexService().getEventSource(event).toString());
3462 json.addProperty("has_comments", event.hasComments());
3463 json.addProperty("has_open_comments", event.hasOpenComments());
3464 json.addProperty("needs_cutting", event.needsCutting());
3465 json.addProperty("has_preview", event.hasPreview());
3466 json.addProperty("agent_id", safeString(event.getAgentId()));
3467 json.addProperty("technical_start", safeString(event.getTechnicalStartTime()));
3468 json.addProperty("technical_end", safeString(event.getTechnicalEndTime()));
3469 json.addProperty("language", safeString(event.getLanguage()));
3470 json.addProperty("language_translation_key", languages.isPresent()
3471 ? safeString(languages.get().get(event.getLanguage())) : "");
3472 json.add("technical_presenters", collectionToJsonArray(event.getTechnicalPresenters()));
3473 json.add("publications", collectionToJsonArray(eventPublicationsToJson(event)));
3474 if (comments.isPresent()) {
3475 json.add("comments", collectionToJsonArray(eventCommentsToJson(comments.get())));
3476 }
3477
3478 return json;
3479 }
3480
3481
3482
3483 private void mergeJsonObjects(JsonObject target, JsonObject source) {
3484 for (String key : source.keySet()) {
3485 target.add(key, source.get(key));
3486 }
3487 }
3488
3489 private JsonObject attachmentToJSON(Attachment attachment) {
3490 JsonObject json = new JsonObject();
3491 mergeJsonObjects(json, getEventMediaPackageElementFields(attachment));
3492 mergeJsonObjects(json, getCommonElementFields(attachment));
3493 return json;
3494 }
3495
3496 private JsonObject catalogToJSON(Catalog catalog) {
3497 JsonObject json = new JsonObject();
3498 mergeJsonObjects(json, getEventMediaPackageElementFields(catalog));
3499 mergeJsonObjects(json, getCommonElementFields(catalog));
3500 return json;
3501 }
3502
3503 private JsonObject trackToJSON(Track track) {
3504 JsonObject json = new JsonObject();
3505 mergeJsonObjects(json, getEventMediaPackageElementFields(track));
3506 mergeJsonObjects(json, getCommonElementFields(track));
3507 json.addProperty("duration", track.getDuration());
3508 json.addProperty("has_audio", track.hasAudio());
3509 json.addProperty("has_video", track.hasVideo());
3510 json.addProperty("has_subtitle", track.hasSubtitle());
3511 json.add("streams", streamsToJSON(track.getStreams()));
3512 return json;
3513 }
3514
3515 private JsonObject streamsToJSON(org.opencastproject.mediapackage.Stream[] streams) {
3516 JsonArray audioArray = new JsonArray();
3517 JsonArray videoArray = new JsonArray();
3518 JsonArray subtitleArray = new JsonArray();
3519
3520 for (org.opencastproject.mediapackage.Stream stream : streams) {
3521 if (stream instanceof AudioStreamImpl) {
3522 AudioStream audioStream = (AudioStream) stream;
3523 JsonObject audioJson = new JsonObject();
3524 audioJson.addProperty("id", safeString(audioStream.getIdentifier()));
3525 audioJson.addProperty("type", safeString(audioStream.getFormat()));
3526 audioJson.addProperty("channels", safeString(audioStream.getChannels()));
3527 audioJson.addProperty("bitrate", audioStream.getBitRate());
3528 audioJson.addProperty("bitdepth", safeString(audioStream.getBitDepth()));
3529 audioJson.addProperty("samplingrate", safeString(audioStream.getSamplingRate()));
3530 audioJson.addProperty("framecount", safeString(audioStream.getFrameCount()));
3531 audioJson.addProperty("peakleveldb", safeString(audioStream.getPkLevDb()));
3532 audioJson.addProperty("rmsleveldb", safeString(audioStream.getRmsLevDb()));
3533 audioJson.addProperty("rmspeakdb", safeString(audioStream.getRmsPkDb()));
3534 audioArray.add(audioJson);
3535
3536 } else if (stream instanceof VideoStreamImpl) {
3537 VideoStream videoStream = (VideoStream) stream;
3538 JsonObject videoJson = new JsonObject();
3539 videoJson.addProperty("id", safeString(videoStream.getIdentifier()));
3540 videoJson.addProperty("type", safeString(videoStream.getFormat()));
3541 videoJson.addProperty("bitrate", videoStream.getBitRate());
3542 videoJson.addProperty("framerate", safeString(videoStream.getFrameRate()));
3543 videoJson.addProperty("resolution", safeString(videoStream.getFrameWidth() + "x"
3544 + videoStream.getFrameHeight()));
3545 videoJson.addProperty("framecount", safeString(videoStream.getFrameCount()));
3546 videoJson.addProperty("scantype", safeString(videoStream.getScanType()));
3547 videoJson.addProperty("scanorder", safeString(videoStream.getScanOrder()));
3548 videoArray.add(videoJson);
3549
3550 } else if (stream instanceof SubtitleStreamImpl) {
3551 SubtitleStreamImpl subtitleStream = (SubtitleStreamImpl) stream;
3552 JsonObject subtitleJson = new JsonObject();
3553 subtitleJson.addProperty("id", safeString(subtitleStream.getIdentifier()));
3554 subtitleJson.addProperty("type", safeString(subtitleStream.getFormat()));
3555 subtitleArray.add(subtitleJson);
3556
3557 } else {
3558 throw new IllegalArgumentException("Stream must be either audio, video, or subtitle");
3559 }
3560 }
3561
3562 JsonObject result = new JsonObject();
3563 result.add("audio", audioArray);
3564 result.add("video", videoArray);
3565 result.add("subtitle", subtitleArray);
3566 return result;
3567 }
3568
3569 private JsonObject publicationToJSON(Publication publication) {
3570 JsonObject json = new JsonObject();
3571
3572 json.addProperty("id", safeString(publication.getIdentifier()));
3573 json.addProperty("channel", safeString(publication.getChannel()));
3574 json.addProperty("mimetype", safeString(publication.getMimeType()));
3575 json.add("tags", arrayToJsonArray(publication.getTags()));
3576 URI uri = signUrl(publication.getURI());
3577 json.addProperty("url", safeString(uri));
3578
3579 JsonObject commonFields = getCommonElementFields(publication);
3580 for (String key : commonFields.keySet()) {
3581 json.add(key, commonFields.get(key));
3582 }
3583
3584 return json;
3585 }
3586
3587 private JsonObject getCommonElementFields(MediaPackageElement element) {
3588 JsonObject fields = new JsonObject();
3589
3590 fields.addProperty("size", element.getSize());
3591 fields.addProperty("checksum", element.getChecksum() != null ? element.getChecksum().getValue() : "");
3592 fields.addProperty("reference", element.getReference() != null ? element.getReference().getIdentifier() : "");
3593
3594 return fields;
3595 }
3596
3597
3598
3599
3600
3601
3602
3603
3604 private JsonArray getEventPublications(Publication[] publications) {
3605 JsonArray publicationJsonArray = new JsonArray();
3606
3607 for (Publication publication : publications) {
3608 JsonObject pubJson = new JsonObject();
3609
3610 pubJson.addProperty("id", safeString(publication.getIdentifier()));
3611 pubJson.addProperty("channel", safeString(publication.getChannel()));
3612 pubJson.addProperty("mimetype", safeString(publication.getMimeType()));
3613 pubJson.add("tags", arrayToJsonArray(publication.getTags()));
3614 pubJson.addProperty("url", safeString(signUrl(publication.getURI())));
3615
3616 publicationJsonArray.add(pubJson);
3617 }
3618
3619 return publicationJsonArray;
3620 }
3621
3622 private URI signUrl(URI url) {
3623 if (url == null) {
3624 return null;
3625 }
3626 if (getUrlSigningService().accepts(url.toString())) {
3627 try {
3628 String clientIP = null;
3629 if (signWithClientIP()) {
3630 clientIP = getSecurityService().getUserIP();
3631 }
3632 return URI.create(getUrlSigningService().sign(url.toString(), getUrlSigningExpireDuration(), null, clientIP));
3633 } catch (UrlSigningException e) {
3634 logger.warn("Unable to sign url '{}'", url, e);
3635 }
3636 }
3637 return url;
3638 }
3639
3640
3641
3642
3643
3644
3645
3646
3647 private JsonArray getEventMediaPackageElements(MediaPackageElement[] elements) {
3648 JsonArray elementJsonArray = new JsonArray();
3649 for (MediaPackageElement element : elements) {
3650 JsonObject elementJson = getEventMediaPackageElementFields(element);
3651 elementJsonArray.add(elementJson);
3652 }
3653 return elementJsonArray;
3654 }
3655
3656 private JsonObject getEventMediaPackageElementFields(MediaPackageElement element) {
3657 JsonObject json = new JsonObject();
3658
3659 json.addProperty("id", safeString(element.getIdentifier()));
3660 json.addProperty("type", safeString(element.getFlavor()));
3661 json.addProperty("mimetype", safeString(element.getMimeType()));
3662 json.add("tags", arrayToJsonArray(element.getTags()));
3663 json.addProperty("url", safeString(signUrl(element.getURI())));
3664
3665 return json;
3666 }
3667
3668 private final Function<Publication, JsonObject> publicationToJson = publication -> {
3669 String channelName = EventUtils.PUBLICATION_CHANNELS.get(publication.getChannel());
3670 if (channelName == null) {
3671 channelName = "EVENTS.EVENTS.DETAILS.PUBLICATIONS.CUSTOM";
3672 }
3673 String url = publication.getURI() == null ? "" : signUrl(publication.getURI()).toString();
3674
3675 JsonObject json = new JsonObject();
3676 json.addProperty("id", publication.getChannel());
3677 json.addProperty("name", channelName);
3678 json.addProperty("url", url);
3679
3680 return json;
3681 };
3682
3683 private JsonObject technicalMetadataToJson(TechnicalMetadata technicalMetadata) {
3684 JsonObject json = new JsonObject();
3685
3686 json.addProperty("agentId", technicalMetadata.getAgentId() != null ? technicalMetadata.getAgentId() : "");
3687 if (technicalMetadata.getCaptureAgentConfiguration() != null) {
3688 json.add("agentConfiguration", mapToJsonObject(technicalMetadata.getCaptureAgentConfiguration()));
3689 } else {
3690 json.add("agentConfiguration", JsonNull.INSTANCE);
3691 }
3692 if (technicalMetadata.getStartDate() != null) {
3693 String startUtc = DateTimeSupport.toUTC(technicalMetadata.getStartDate().getTime());
3694 json.addProperty("start", startUtc);
3695 } else {
3696 json.addProperty("start", "");
3697 }
3698 if (technicalMetadata.getEndDate() != null) {
3699 String endUtc = DateTimeSupport.toUTC(technicalMetadata.getEndDate().getTime());
3700 json.addProperty("end", endUtc);
3701 } else {
3702 json.addProperty("end", "");
3703 }
3704 String eventId = technicalMetadata.getEventId();
3705 json.addProperty("eventId", safeString(eventId));
3706 json.add("presenters", collectionToJsonArray(technicalMetadata.getPresenters()));
3707 Optional<Recording> optRecording = technicalMetadata.getRecording();
3708 if (optRecording.isPresent()) {
3709 json.add("recording", recordingToJson(optRecording.get()));
3710 }
3711
3712 return json;
3713 }
3714
3715 public static JsonObject recordingToJson(Recording recording) {
3716 JsonObject json = new JsonObject();
3717
3718 json.addProperty("id", safeString(recording.getID()));
3719 json.addProperty("lastCheckInTime", recording.getLastCheckinTime() != null ? recording.getLastCheckinTime() : 0L);
3720 json.addProperty("lastCheckInTimeUTC", recording.getLastCheckinTime() != null
3721 ? toUTC(recording.getLastCheckinTime()) : "");
3722 json.addProperty("state", safeString(recording.getState()));
3723
3724 return json;
3725 }
3726
3727 @PUT
3728 @Path("{eventId}/workflows/{workflowId}/action/{action}")
3729 @RestQuery(
3730 name = "workflowAction",
3731 description = "Performs the given action for the given workflow.",
3732 returnDescription = "",
3733 pathParameters = {
3734 @RestParameter(name = "eventId", description = "The id of the media package", isRequired = true,
3735 type = RestParameter.Type.STRING),
3736 @RestParameter(name = "workflowId", description = "The id of the workflow", isRequired = true,
3737 type = RestParameter.Type.STRING),
3738 @RestParameter(name = "action", description = "The action to take: STOP, RETRY or NONE (abort processing)",
3739 isRequired = true, type = RestParameter.Type.STRING)
3740 },
3741 responses = {
3742 @RestResponse(responseCode = SC_OK, description = "Workflow resumed."),
3743 @RestResponse(responseCode = SC_NOT_FOUND, description = "Event or workflow instance not found."),
3744 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Invalid action entered."),
3745 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to perform the "
3746 + "action. Maybe you need to authenticate."),
3747 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "An exception occurred.")
3748 })
3749 public Response workflowAction(@PathParam("eventId") String id, @PathParam("workflowId") long wfId,
3750 @PathParam("action") String action) {
3751 if (StringUtils.isEmpty(id) || StringUtils.isEmpty(action)) {
3752 return badRequest();
3753 }
3754
3755 try {
3756 final Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
3757 if (optEvent.isEmpty()) {
3758 return notFound("Cannot find an event with id '%s'.", id);
3759 }
3760
3761 final WorkflowInstance wfInstance = getWorkflowService().getWorkflowById(wfId);
3762 if (!wfInstance.getMediaPackage().getIdentifier().toString().equals(id)) {
3763 return badRequest(String.format("Workflow %s is not associated to event %s", wfId, id));
3764 }
3765
3766 if (RetryStrategy.NONE.toString().equalsIgnoreCase(action)
3767 || RetryStrategy.RETRY.toString().equalsIgnoreCase(action)) {
3768 getWorkflowService().resume(wfId, Collections.singletonMap("retryStrategy", action));
3769 return ok();
3770 }
3771
3772 if (WORKFLOW_ACTION_STOP.equalsIgnoreCase(action)) {
3773 getWorkflowService().stop(wfId);
3774 return ok();
3775 }
3776
3777 return badRequest("Action not supported: " + action);
3778 } catch (NotFoundException e) {
3779 return notFound("Workflow not found: '%d'.", wfId);
3780 } catch (IllegalStateException e) {
3781 return badRequest(String.format("Action %s not allowed for current workflow state. EventId: %s", action, id));
3782 } catch (UnauthorizedException e) {
3783 return forbidden();
3784 } catch (Exception e) {
3785 return serverError();
3786 }
3787 }
3788
3789 @DELETE
3790 @Path("{eventId}/workflows/{workflowId}")
3791 @RestQuery(
3792 name = "deleteWorkflow",
3793 description = "Deletes a workflow",
3794 returnDescription = "The method doesn't return any content",
3795 pathParameters = {
3796 @RestParameter(name = "eventId", isRequired = true, description = "The event identifier",
3797 type = RestParameter.Type.STRING),
3798 @RestParameter(name = "workflowId", isRequired = true, description = "The workflow identifier",
3799 type = RestParameter.Type.INTEGER)
3800 },
3801 responses = {
3802 @RestResponse(responseCode = SC_BAD_REQUEST, description = "When trying to delete the latest workflow of the "
3803 + "event."),
3804 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the event or the workflow has not been found."),
3805 @RestResponse(responseCode = SC_NO_CONTENT, description = "The method does not return any content")
3806 })
3807 public Response deleteWorkflow(@PathParam("eventId") String id, @PathParam("workflowId") long wfId)
3808 throws SearchIndexException {
3809 final Optional<Event> optEvent = getIndexService().getEvent(id, getIndex());
3810 try {
3811 if (optEvent.isEmpty()) {
3812 return notFound("Cannot find an event with id '%s'.", id);
3813 }
3814
3815 final WorkflowInstance wfInstance = getWorkflowService().getWorkflowById(wfId);
3816 if (!wfInstance.getMediaPackage().getIdentifier().toString().equals(id)) {
3817 return badRequest(String.format("Workflow %s is not associated to event %s", wfId, id));
3818 }
3819
3820 if (wfId == optEvent.get().getWorkflowId()) {
3821 return badRequest(String.format("Cannot delete current workflow %s from event %s."
3822 + " Only older workflows can be deleted.", wfId, id));
3823 }
3824
3825 getWorkflowService().remove(wfId);
3826
3827 return Response.noContent().build();
3828 } catch (WorkflowStateException e) {
3829 return badRequest("Deleting is not allowed for current workflow state. EventId: " + id);
3830 } catch (NotFoundException e) {
3831 return notFound("Workflow not found: '%d'.", wfId);
3832 } catch (UnauthorizedException e) {
3833 return forbidden();
3834 } catch (Exception e) {
3835 return serverError();
3836 }
3837 }
3838
3839 private Optional<Event> checkAgentAccessForEvent(final String eventId)
3840 throws UnauthorizedException, SearchIndexException {
3841 final Optional<Event> event = getIndexService().getEvent(eventId, getIndex());
3842 if (event.isEmpty() || !event.get().getEventStatus().contains("SCHEDULE")) {
3843 return event;
3844 }
3845 SecurityUtil.checkAgentAccess(getSecurityService(), event.get().getAgentId());
3846 return event;
3847 }
3848
3849 private void checkAgentAccessForAgent(final String agentId) throws UnauthorizedException {
3850 SecurityUtil.checkAgentAccess(getSecurityService(), agentId);
3851 }
3852
3853 }