1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.index.service.impl.util;
22
23 import org.opencastproject.elasticsearch.index.objects.event.Event;
24 import org.opencastproject.index.service.exception.IndexServiceException;
25 import org.opencastproject.index.service.util.RequestUtils;
26 import org.opencastproject.ingest.api.IngestException;
27 import org.opencastproject.ingest.api.IngestService;
28 import org.opencastproject.mediapackage.MediaPackage;
29 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
30 import org.opencastproject.mediapackage.MediaPackageElements;
31 import org.opencastproject.mediapackage.MediaPackageException;
32 import org.opencastproject.metadata.dublincore.DublinCore;
33 import org.opencastproject.metadata.dublincore.DublinCoreMetadataCollection;
34 import org.opencastproject.metadata.dublincore.EventCatalogUIAdapter;
35 import org.opencastproject.metadata.dublincore.MetadataField;
36 import org.opencastproject.metadata.dublincore.MetadataJson;
37 import org.opencastproject.metadata.dublincore.MetadataList;
38 import org.opencastproject.security.api.AccessControlEntry;
39 import org.opencastproject.security.api.AccessControlList;
40 import org.opencastproject.util.NotFoundException;
41
42 import org.apache.commons.fileupload.FileItemIterator;
43 import org.apache.commons.fileupload.FileItemStream;
44 import org.apache.commons.fileupload.FileUploadException;
45 import org.apache.commons.fileupload.servlet.ServletFileUpload;
46 import org.apache.commons.fileupload.util.Streams;
47 import org.apache.commons.lang3.StringUtils;
48 import org.joda.time.DateTime;
49 import org.joda.time.DateTimeZone;
50 import org.json.simple.JSONArray;
51 import org.json.simple.JSONObject;
52 import org.json.simple.parser.JSONParser;
53 import org.json.simple.parser.ParseException;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import java.io.IOException;
58 import java.text.SimpleDateFormat;
59 import java.util.ArrayList;
60 import java.util.List;
61 import java.util.ListIterator;
62 import java.util.Map;
63 import java.util.Optional;
64 import java.util.TimeZone;
65
66 import javax.servlet.http.HttpServletRequest;
67
68 public class EventHttpServletRequest {
69
70 private static final Logger logger = LoggerFactory.getLogger(EventHttpServletRequest.class);
71
72 private static final String ACTION_JSON_KEY = "action";
73 private static final String ALLOW_JSON_KEY = "allow";
74 private static final String METADATA_JSON_KEY = "metadata";
75 private static final String ROLE_JSON_KEY = "role";
76
77 private Optional<AccessControlList> acl = Optional.empty();
78 private Optional<MediaPackage> mediaPackage = Optional.empty();
79 private Optional<MetadataList> metadataList = Optional.empty();
80 private Optional<JSONObject> processing = Optional.empty();
81 private Optional<JSONObject> source = Optional.empty();
82 private Optional<JSONObject> scheduling = Optional.empty();
83
84 public void setAcl(AccessControlList acl) {
85 this.acl = Optional.of(acl);
86 }
87
88 public void setMediaPackage(MediaPackage mediaPackage) {
89 this.mediaPackage = Optional.of(mediaPackage);
90 }
91
92 public void setMetadataList(MetadataList metadataList) {
93 this.metadataList = Optional.of(metadataList);
94 }
95
96 public void setProcessing(JSONObject processing) {
97 this.processing = Optional.of(processing);
98 }
99
100 public void setScheduling(JSONObject scheduling) {
101 this.scheduling = Optional.of(scheduling);
102 }
103
104 public void setSource(JSONObject source) {
105 this.source = Optional.of(source);
106 }
107
108 public Optional<AccessControlList> getAcl() {
109 return acl;
110 }
111
112 public Optional<MediaPackage> getMediaPackage() {
113 return mediaPackage;
114 }
115
116 public Optional<MetadataList> getMetadataList() {
117 return metadataList;
118 }
119
120 public Optional<JSONObject> getProcessing() {
121 return processing;
122 }
123
124 public Optional<JSONObject> getScheduling() {
125 return scheduling;
126 }
127
128 public Optional<JSONObject> getSource() {
129 return source;
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 public static EventHttpServletRequest createFromHttpServletRequest(
152 HttpServletRequest request,
153 IngestService ingestService,
154 List<EventCatalogUIAdapter> eventCatalogUIAdapters,
155 String startDatePattern,
156 String startTimePattern)
157 throws IndexServiceException {
158 EventHttpServletRequest eventHttpServletRequest = new EventHttpServletRequest();
159 try {
160 if (ServletFileUpload.isMultipartContent(request)) {
161 eventHttpServletRequest.setMediaPackage(ingestService.createMediaPackage());
162 if (eventHttpServletRequest.getMediaPackage().isEmpty()) {
163 throw new IndexServiceException("Unable to create a new mediapackage to store the new event's media.");
164 }
165
166 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
167 FileItemStream item = iter.next();
168 String fieldName = item.getFieldName();
169 if (item.isFormField()) {
170 setFormField(eventCatalogUIAdapters, eventHttpServletRequest, item, fieldName, startDatePattern,
171 startTimePattern);
172 } else {
173 if (!item.getName().isBlank()) {
174 ingestFile(ingestService, eventHttpServletRequest, item);
175 } else {
176 logger.debug("Skipping field {} due to missing filename", item.getFieldName());
177 }
178 }
179 }
180 } else {
181 throw new IllegalArgumentException("No multipart content");
182 }
183
184 return eventHttpServletRequest;
185
186 } catch (Exception e) {
187 throw new IndexServiceException("Unable to parse new event.", e);
188 }
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207 private static void ingestFile(IngestService ingestService, EventHttpServletRequest eventHttpServletRequest,
208 FileItemStream item) throws MediaPackageException, IOException, IngestException {
209 MediaPackage mp = eventHttpServletRequest.getMediaPackage().get();
210 if ("presenter".equals(item.getFieldName())) {
211 eventHttpServletRequest.setMediaPackage(
212 ingestService.addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTER_SOURCE, mp));
213 } else if ("presentation".equals(item.getFieldName())) {
214 eventHttpServletRequest.setMediaPackage(
215 ingestService.addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTATION_SOURCE, mp));
216 } else if ("audio".equals(item.getFieldName())) {
217 eventHttpServletRequest.setMediaPackage(ingestService.addTrack(item.openStream(), item.getName(),
218 new MediaPackageElementFlavor("presenter-audio", "source"), mp));
219 } else {
220 logger.warn("Unknown field name found {}", item.getFieldName());
221 }
222 }
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 private static void setFormField(List<EventCatalogUIAdapter> eventCatalogUIAdapters,
245 EventHttpServletRequest eventHttpServletRequest,
246 FileItemStream item,
247 String fieldName,
248 String startDatePattern,
249 String startTimePattern)
250 throws IOException, NotFoundException {
251 if (METADATA_JSON_KEY.equals(fieldName)) {
252 String metadata = Streams.asString(item.openStream());
253 if (StringUtils.isNotEmpty(metadata)) {
254 try {
255 MetadataList metadataList = deserializeMetadataList(metadata, eventCatalogUIAdapters, startDatePattern,
256 startTimePattern);
257 eventHttpServletRequest.setMetadataList(metadataList);
258 } catch (IllegalArgumentException e) {
259 throw e;
260 } catch (ParseException e) {
261 throw new IllegalArgumentException(String.format("Unable to parse event metadata because: '%s'", e));
262 } catch (NotFoundException e) {
263 throw e;
264 } catch (java.text.ParseException e) {
265 throw new IllegalArgumentException(String.format("Unable to parse event metadata because: '%s'", e));
266 }
267 }
268 } else if ("acl".equals(item.getFieldName())) {
269 String access = Streams.asString(item.openStream());
270 if (StringUtils.isNotEmpty(access)) {
271 try {
272 AccessControlList acl = deserializeJsonToAcl(access, true);
273 eventHttpServletRequest.setAcl(acl);
274 } catch (Exception e) {
275 logger.warn("Unable to parse acl {}", access);
276 throw new IllegalArgumentException("Unable to parse acl");
277 }
278 }
279 } else if ("processing".equals(item.getFieldName())) {
280 String processing = Streams.asString(item.openStream());
281 if (StringUtils.isNotEmpty(processing)) {
282 JSONParser parser = new JSONParser();
283 try {
284 eventHttpServletRequest.setProcessing((JSONObject) parser.parse(processing));
285 } catch (Exception e) {
286 logger.warn("Unable to parse processing configuration {}", processing);
287 throw new IllegalArgumentException("Unable to parse processing configuration");
288 }
289 }
290 } else if ("scheduling".equals(item.getFieldName())) {
291 String scheduling = Streams.asString(item.openStream());
292 if (StringUtils.isNotEmpty(scheduling)) {
293 JSONParser parser = new JSONParser();
294 try {
295 eventHttpServletRequest.setScheduling((JSONObject) parser.parse(scheduling));
296 } catch (Exception e) {
297 logger.warn("Unable to parse scheduling information {}", scheduling);
298 throw new IllegalArgumentException("Unable to parse scheduling information");
299 }
300 }
301 }
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 public static EventHttpServletRequest updateFromHttpServletRequest(
326 Event event,
327 HttpServletRequest request,
328 List<EventCatalogUIAdapter> eventCatalogUIAdapters,
329 String startDatePattern,
330 String startTimePattern)
331 throws IllegalArgumentException, IndexServiceException, NotFoundException {
332 EventHttpServletRequest eventHttpServletRequest = new EventHttpServletRequest();
333 if (ServletFileUpload.isMultipartContent(request)) {
334 try {
335 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
336 FileItemStream item = iter.next();
337 String fieldName = item.getFieldName();
338 if (item.isFormField()) {
339 setFormField(eventCatalogUIAdapters, eventHttpServletRequest, item, fieldName, startDatePattern,
340 startTimePattern);
341 }
342 }
343 } catch (IOException e) {
344 throw new IndexServiceException("Unable to update event", e);
345 } catch (FileUploadException e) {
346 throw new IndexServiceException("Unable to update event", e);
347 }
348 } else {
349 throw new IllegalArgumentException("No multipart content");
350 }
351 return eventHttpServletRequest;
352 }
353
354
355
356
357
358
359
360
361
362
363
364 protected static AccessControlList deserializeJsonToAcl(String json, boolean assumeAllow) throws ParseException {
365 JSONParser parser = new JSONParser();
366 JSONArray aclJson = (JSONArray) parser.parse(json);
367 @SuppressWarnings("unchecked")
368 ListIterator<Object> iterator = aclJson.listIterator();
369 JSONObject aceJson;
370 List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>();
371 while (iterator.hasNext()) {
372 aceJson = (JSONObject) iterator.next();
373 String action = aceJson.get(ACTION_JSON_KEY) != null ? aceJson.get(ACTION_JSON_KEY).toString() : "";
374 String allow;
375 if (assumeAllow) {
376 allow = "true";
377 } else {
378 allow = aceJson.get(ALLOW_JSON_KEY) != null ? aceJson.get(ALLOW_JSON_KEY).toString() : "";
379 }
380 String role = aceJson.get(ROLE_JSON_KEY) != null ? aceJson.get(ROLE_JSON_KEY).toString() : "";
381 if (StringUtils.trimToNull(action) != null && StringUtils.trimToNull(allow) != null
382 && StringUtils.trimToNull(role) != null) {
383 AccessControlEntry ace = new AccessControlEntry(role, action, Boolean.parseBoolean(allow));
384 entries.add(ace);
385 } else {
386 throw new IllegalArgumentException(String.format(
387 "One of the access control elements is missing a property. The action was '%s', allow was '%s' and "
388 + "the role was '%s'",
389 action, allow, role));
390 }
391 }
392 return new AccessControlList(entries);
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 protected static MetadataList deserializeMetadataList(
411 String json,
412 List<EventCatalogUIAdapter> catalogAdapters,
413 String startDatePattern,
414 String startTimePattern)
415 throws ParseException, NotFoundException, java.text.ParseException {
416 MetadataList metadataList = new MetadataList();
417 JSONParser parser = new JSONParser();
418 JSONArray jsonCatalogs = (JSONArray) parser.parse(json);
419 for (int i = 0; i < jsonCatalogs.size(); i++) {
420 JSONObject catalog = (JSONObject) jsonCatalogs.get(i);
421 if (catalog.get("flavor") == null || StringUtils.isBlank(catalog.get("flavor").toString())) {
422 throw new IllegalArgumentException(
423 "Unable to create new event as no flavor was given for one of the metadata collections");
424 }
425 String flavorString = catalog.get("flavor").toString();
426 MediaPackageElementFlavor flavor = MediaPackageElementFlavor.parseFlavor(flavorString);
427
428 DublinCoreMetadataCollection collection = null;
429 EventCatalogUIAdapter adapter = null;
430 for (EventCatalogUIAdapter eventCatalogUIAdapter : catalogAdapters) {
431 if (eventCatalogUIAdapter.getFlavor().equals(flavor)) {
432 adapter = eventCatalogUIAdapter;
433 collection = eventCatalogUIAdapter.getRawFields();
434 }
435 }
436
437 if (collection == null) {
438 throw new IllegalArgumentException(
439 String.format("Unable to find an EventCatalogUIAdapter with Flavor '%s'", flavorString));
440 }
441
442 String fieldsJson = catalog.get("fields").toString();
443 if (StringUtils.trimToNull(fieldsJson) != null) {
444 Map<String, String> fields = RequestUtils.getKeyValueMap(fieldsJson);
445 for (String key : fields.keySet()) {
446 if ("subjects".equals(key)) {
447
448 MetadataField field = collection.getOutputFields().get(DublinCore.PROPERTY_SUBJECT.getLocalName());
449 if (field == null) {
450 throw new NotFoundException(String.format(
451 "Cannot find a metadata field with id 'subject' from Catalog with Flavor '%s'.", flavorString));
452 }
453 collection.removeField(field);
454 try {
455 JSONArray subjects = (JSONArray) parser.parse(fields.get(key));
456 collection.addField(MetadataJson
457 .copyWithDifferentJsonValue(field, StringUtils.join(subjects.iterator(), ",")));
458 } catch (ParseException e) {
459 throw new IllegalArgumentException(
460 String.format("Unable to parse the 'subjects' metadata array field because: %s", e.toString()));
461 }
462 } else if ("startDate".equals(key)) {
463
464
465 MetadataField field = collection.getOutputFields().get(key);
466 if (field == null) {
467 throw new NotFoundException(String.format(
468 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
469 }
470 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startDatePattern == null
471 ? field.getPattern() : startDatePattern);
472 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
473 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
474 if (field.getValue() != null) {
475 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
476 newStartDate = oldStartDate.withDate(newStartDate.year().get(), newStartDate.monthOfYear().get(),
477 newStartDate.dayOfMonth().get());
478 }
479 collection.removeField(field);
480 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
481 } else if ("startTime".equals(key)) {
482
483
484 MetadataField field = collection.getOutputFields().get("startDate");
485 if (field == null) {
486 throw new NotFoundException(String.format(
487 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", "startDate",
488 flavorString));
489 }
490 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startTimePattern == null
491 ? "HH:mm" : startTimePattern);
492 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
493 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
494 if (field.getValue() != null) {
495 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
496 newStartDate = oldStartDate.withTime(
497 newStartDate.hourOfDay().get(),
498 newStartDate.minuteOfHour().get(),
499 newStartDate.secondOfMinute().get(),
500 newStartDate.millisOfSecond().get());
501 }
502 collection.removeField(field);
503 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
504 } else {
505 MetadataField field = collection.getOutputFields().get(key);
506 if (field == null) {
507 throw new NotFoundException(String.format(
508 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
509 }
510 collection.removeField(field);
511 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, fields.get(key)));
512 }
513 }
514 }
515 metadataList.add(adapter, collection);
516 }
517 setStartDateAndTimeIfUnset(metadataList);
518 return metadataList;
519 }
520
521
522
523
524
525
526
527 private static void setStartDateAndTimeIfUnset(MetadataList metadataList) {
528 final DublinCoreMetadataCollection commonEventCollection = metadataList
529 .getMetadataByFlavor(MediaPackageElements.EPISODE.toString());
530 if (commonEventCollection != null) {
531 MetadataField startDate = commonEventCollection.getOutputFields().get("startDate");
532 if (!startDate.isUpdated()) {
533 SimpleDateFormat utcDateFormat = new SimpleDateFormat(startDate.getPattern());
534 utcDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
535 String currentDate = utcDateFormat.format(new DateTime(DateTimeZone.UTC).toDate());
536 commonEventCollection.removeField(startDate);
537 commonEventCollection.addField(MetadataJson.copyWithDifferentJsonValue(startDate, currentDate));
538 }
539 }
540 }
541 }