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, startTimePattern);
171 } else {
172 if (!item.getName().isBlank()) {
173 ingestFile(ingestService, eventHttpServletRequest, item);
174 } else {
175 logger.debug("Skipping field {} due to missing filename", item.getFieldName());
176 }
177 }
178 }
179 } else {
180 throw new IllegalArgumentException("No multipart content");
181 }
182
183 return eventHttpServletRequest;
184
185 } catch (Exception e) {
186 throw new IndexServiceException("Unable to parse new event.", e);
187 }
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 private static void ingestFile(IngestService ingestService, EventHttpServletRequest eventHttpServletRequest,
207 FileItemStream item) throws MediaPackageException, IOException, IngestException {
208 MediaPackage mp = eventHttpServletRequest.getMediaPackage().get();
209 if ("presenter".equals(item.getFieldName())) {
210 eventHttpServletRequest.setMediaPackage(
211 ingestService.addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTER_SOURCE, mp));
212 } else if ("presentation".equals(item.getFieldName())) {
213 eventHttpServletRequest.setMediaPackage(
214 ingestService.addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTATION_SOURCE, mp));
215 } else if ("audio".equals(item.getFieldName())) {
216 eventHttpServletRequest.setMediaPackage(ingestService.addTrack(item.openStream(), item.getName(),
217 new MediaPackageElementFlavor("presenter-audio", "source"), mp));
218 } else {
219 logger.warn("Unknown field name found {}", item.getFieldName());
220 }
221 }
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 private static void setFormField(List<EventCatalogUIAdapter> eventCatalogUIAdapters,
244 EventHttpServletRequest eventHttpServletRequest,
245 FileItemStream item,
246 String fieldName,
247 String startDatePattern,
248 String startTimePattern)
249 throws IOException, NotFoundException {
250 if (METADATA_JSON_KEY.equals(fieldName)) {
251 String metadata = Streams.asString(item.openStream());
252 if (StringUtils.isNotEmpty(metadata)) {
253 try {
254 MetadataList metadataList = deserializeMetadataList(metadata, eventCatalogUIAdapters, startDatePattern,
255 startTimePattern);
256 eventHttpServletRequest.setMetadataList(metadataList);
257 } catch (IllegalArgumentException e) {
258 throw e;
259 } catch (ParseException e) {
260 throw new IllegalArgumentException(String.format("Unable to parse event metadata because: '%s'", e.toString()));
261 } catch (NotFoundException e) {
262 throw e;
263 } catch (java.text.ParseException e) {
264 throw new IllegalArgumentException(String.format("Unable to parse event metadata because: '%s'", e.toString()));
265 }
266 }
267 } else if ("acl".equals(item.getFieldName())) {
268 String access = Streams.asString(item.openStream());
269 if (StringUtils.isNotEmpty(access)) {
270 try {
271 AccessControlList acl = deserializeJsonToAcl(access, true);
272 eventHttpServletRequest.setAcl(acl);
273 } catch (Exception e) {
274 logger.warn("Unable to parse acl {}", access);
275 throw new IllegalArgumentException("Unable to parse acl");
276 }
277 }
278 } else if ("processing".equals(item.getFieldName())) {
279 String processing = Streams.asString(item.openStream());
280 if (StringUtils.isNotEmpty(processing)) {
281 JSONParser parser = new JSONParser();
282 try {
283 eventHttpServletRequest.setProcessing((JSONObject) parser.parse(processing));
284 } catch (Exception e) {
285 logger.warn("Unable to parse processing configuration {}", processing);
286 throw new IllegalArgumentException("Unable to parse processing configuration");
287 }
288 }
289 } else if ("scheduling".equals(item.getFieldName())) {
290 String scheduling = Streams.asString(item.openStream());
291 if (StringUtils.isNotEmpty(scheduling)) {
292 JSONParser parser = new JSONParser();
293 try {
294 eventHttpServletRequest.setScheduling((JSONObject) parser.parse(scheduling));
295 } catch (Exception e) {
296 logger.warn("Unable to parse scheduling information {}", scheduling);
297 throw new IllegalArgumentException("Unable to parse scheduling information");
298 }
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 public static EventHttpServletRequest updateFromHttpServletRequest(
325 Event event,
326 HttpServletRequest request,
327 List<EventCatalogUIAdapter> eventCatalogUIAdapters,
328 String startDatePattern,
329 String startTimePattern)
330 throws IllegalArgumentException, IndexServiceException, NotFoundException {
331 EventHttpServletRequest eventHttpServletRequest = new EventHttpServletRequest();
332 if (ServletFileUpload.isMultipartContent(request)) {
333 try {
334 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
335 FileItemStream item = iter.next();
336 String fieldName = item.getFieldName();
337 if (item.isFormField()) {
338 setFormField(eventCatalogUIAdapters, eventHttpServletRequest, item, fieldName, startDatePattern, startTimePattern);
339 }
340 }
341 } catch (IOException e) {
342 throw new IndexServiceException("Unable to update event", e);
343 } catch (FileUploadException e) {
344 throw new IndexServiceException("Unable to update event", e);
345 }
346 } else {
347 throw new IllegalArgumentException("No multipart content");
348 }
349 return eventHttpServletRequest;
350 }
351
352
353
354
355
356
357
358
359
360
361
362 protected static AccessControlList deserializeJsonToAcl(String json, boolean assumeAllow) throws ParseException {
363 JSONParser parser = new JSONParser();
364 JSONArray aclJson = (JSONArray) parser.parse(json);
365 @SuppressWarnings("unchecked")
366 ListIterator<Object> iterator = aclJson.listIterator();
367 JSONObject aceJson;
368 List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>();
369 while (iterator.hasNext()) {
370 aceJson = (JSONObject) iterator.next();
371 String action = aceJson.get(ACTION_JSON_KEY) != null ? aceJson.get(ACTION_JSON_KEY).toString() : "";
372 String allow;
373 if (assumeAllow) {
374 allow = "true";
375 } else {
376 allow = aceJson.get(ALLOW_JSON_KEY) != null ? aceJson.get(ALLOW_JSON_KEY).toString() : "";
377 }
378 String role = aceJson.get(ROLE_JSON_KEY) != null ? aceJson.get(ROLE_JSON_KEY).toString() : "";
379 if (StringUtils.trimToNull(action) != null && StringUtils.trimToNull(allow) != null
380 && StringUtils.trimToNull(role) != null) {
381 AccessControlEntry ace = new AccessControlEntry(role, action, Boolean.parseBoolean(allow));
382 entries.add(ace);
383 } else {
384 throw new IllegalArgumentException(String.format(
385 "One of the access control elements is missing a property. The action was '%s', allow was '%s' and the role was '%s'",
386 action, allow, role));
387 }
388 }
389 return new AccessControlList(entries);
390 }
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 protected static MetadataList deserializeMetadataList(
408 String json,
409 List<EventCatalogUIAdapter> catalogAdapters,
410 String startDatePattern,
411 String startTimePattern)
412 throws ParseException, NotFoundException, java.text.ParseException {
413 MetadataList metadataList = new MetadataList();
414 JSONParser parser = new JSONParser();
415 JSONArray jsonCatalogs = (JSONArray) parser.parse(json);
416 for (int i = 0; i < jsonCatalogs.size(); i++) {
417 JSONObject catalog = (JSONObject) jsonCatalogs.get(i);
418 if (catalog.get("flavor") == null || StringUtils.isBlank(catalog.get("flavor").toString())) {
419 throw new IllegalArgumentException(
420 "Unable to create new event as no flavor was given for one of the metadata collections");
421 }
422 String flavorString = catalog.get("flavor").toString();
423 MediaPackageElementFlavor flavor = MediaPackageElementFlavor.parseFlavor(flavorString);
424
425 DublinCoreMetadataCollection collection = null;
426 EventCatalogUIAdapter adapter = null;
427 for (EventCatalogUIAdapter eventCatalogUIAdapter : catalogAdapters) {
428 if (eventCatalogUIAdapter.getFlavor().equals(flavor)) {
429 adapter = eventCatalogUIAdapter;
430 collection = eventCatalogUIAdapter.getRawFields();
431 }
432 }
433
434 if (collection == null) {
435 throw new IllegalArgumentException(
436 String.format("Unable to find an EventCatalogUIAdapter with Flavor '%s'", flavorString));
437 }
438
439 String fieldsJson = catalog.get("fields").toString();
440 if (StringUtils.trimToNull(fieldsJson) != null) {
441 Map<String, String> fields = RequestUtils.getKeyValueMap(fieldsJson);
442 for (String key : fields.keySet()) {
443 if ("subjects".equals(key)) {
444
445 MetadataField field = collection.getOutputFields().get(DublinCore.PROPERTY_SUBJECT.getLocalName());
446 if (field == null) {
447 throw new NotFoundException(String.format(
448 "Cannot find a metadata field with id 'subject' from Catalog with Flavor '%s'.", flavorString));
449 }
450 collection.removeField(field);
451 try {
452 JSONArray subjects = (JSONArray) parser.parse(fields.get(key));
453 collection.addField(MetadataJson
454 .copyWithDifferentJsonValue(field, StringUtils.join(subjects.iterator(), ",")));
455 } catch (ParseException e) {
456 throw new IllegalArgumentException(
457 String.format("Unable to parse the 'subjects' metadata array field because: %s", e.toString()));
458 }
459 } else if ("startDate".equals(key)) {
460
461 MetadataField field = collection.getOutputFields().get(key);
462 if (field == null) {
463 throw new NotFoundException(String.format(
464 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
465 }
466 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startDatePattern == null ? field.getPattern() : startDatePattern);
467 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
468 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
469 if (field.getValue() != null) {
470 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
471 newStartDate = oldStartDate.withDate(newStartDate.year().get(), newStartDate.monthOfYear().get(), newStartDate.dayOfMonth().get());
472 }
473 collection.removeField(field);
474 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
475 } else if ("startTime".equals(key)) {
476
477 MetadataField field = collection.getOutputFields().get("startDate");
478 if (field == null) {
479 throw new NotFoundException(String.format(
480 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", "startDate", flavorString));
481 }
482 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startTimePattern == null ? "HH:mm" : startTimePattern);
483 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
484 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
485 if (field.getValue() != null) {
486 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
487 newStartDate = oldStartDate.withTime(
488 newStartDate.hourOfDay().get(),
489 newStartDate.minuteOfHour().get(),
490 newStartDate.secondOfMinute().get(),
491 newStartDate.millisOfSecond().get());
492 }
493 collection.removeField(field);
494 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
495 } else {
496 MetadataField field = collection.getOutputFields().get(key);
497 if (field == null) {
498 throw new NotFoundException(String.format(
499 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
500 }
501 collection.removeField(field);
502 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, fields.get(key)));
503 }
504 }
505 }
506 metadataList.add(adapter, collection);
507 }
508 setStartDateAndTimeIfUnset(metadataList);
509 return metadataList;
510 }
511
512
513
514
515
516
517
518 private static void setStartDateAndTimeIfUnset(MetadataList metadataList) {
519 final DublinCoreMetadataCollection commonEventCollection = metadataList
520 .getMetadataByFlavor(MediaPackageElements.EPISODE.toString());
521 if (commonEventCollection != null) {
522 MetadataField startDate = commonEventCollection.getOutputFields().get("startDate");
523 if (!startDate.isUpdated()) {
524 SimpleDateFormat utcDateFormat = new SimpleDateFormat(startDate.getPattern());
525 utcDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
526 String currentDate = utcDateFormat.format(new DateTime(DateTimeZone.UTC).toDate());
527 commonEventCollection.removeField(startDate);
528 commonEventCollection.addField(MetadataJson.copyWithDifferentJsonValue(startDate, currentDate));
529 }
530 }
531 }
532 }