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 com.entwinemedia.fn.data.Opt;
43
44 import org.apache.commons.fileupload.FileItemIterator;
45 import org.apache.commons.fileupload.FileItemStream;
46 import org.apache.commons.fileupload.FileUploadException;
47 import org.apache.commons.fileupload.servlet.ServletFileUpload;
48 import org.apache.commons.fileupload.util.Streams;
49 import org.apache.commons.lang3.StringUtils;
50 import org.joda.time.DateTime;
51 import org.joda.time.DateTimeZone;
52 import org.json.simple.JSONArray;
53 import org.json.simple.JSONObject;
54 import org.json.simple.parser.JSONParser;
55 import org.json.simple.parser.ParseException;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import java.io.IOException;
60 import java.text.SimpleDateFormat;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.ListIterator;
64 import java.util.Map;
65 import java.util.TimeZone;
66
67 import javax.servlet.http.HttpServletRequest;
68
69 public class EventHttpServletRequest {
70
71 private static final Logger logger = LoggerFactory.getLogger(EventHttpServletRequest.class);
72
73 private static final String ACTION_JSON_KEY = "action";
74 private static final String ALLOW_JSON_KEY = "allow";
75 private static final String METADATA_JSON_KEY = "metadata";
76 private static final String ROLE_JSON_KEY = "role";
77
78 private Opt<AccessControlList> acl = Opt.none();
79 private Opt<MediaPackage> mediaPackage = Opt.none();
80 private Opt<MetadataList> metadataList = Opt.none();
81 private Opt<JSONObject> processing = Opt.none();
82 private Opt<JSONObject> source = Opt.none();
83 private Opt<JSONObject> scheduling = Opt.none();
84
85 public void setAcl(AccessControlList acl) {
86 this.acl = Opt.some(acl);
87 }
88
89 public void setMediaPackage(MediaPackage mediaPackage) {
90 this.mediaPackage = Opt.some(mediaPackage);
91 }
92
93 public void setMetadataList(MetadataList metadataList) {
94 this.metadataList = Opt.some(metadataList);
95 }
96
97 public void setProcessing(JSONObject processing) {
98 this.processing = Opt.some(processing);
99 }
100
101 public void setScheduling(JSONObject scheduling) {
102 this.scheduling = Opt.some(scheduling);
103 }
104
105 public void setSource(JSONObject source) {
106 this.source = Opt.some(source);
107 }
108
109 public Opt<AccessControlList> getAcl() {
110 return acl;
111 }
112
113 public Opt<MediaPackage> getMediaPackage() {
114 return mediaPackage;
115 }
116
117 public Opt<MetadataList> getMetadataList() {
118 return metadataList;
119 }
120
121 public Opt<JSONObject> getProcessing() {
122 return processing;
123 }
124
125 public Opt<JSONObject> getScheduling() {
126 return scheduling;
127 }
128
129 public Opt<JSONObject> getSource() {
130 return source;
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 public static EventHttpServletRequest createFromHttpServletRequest(
153 HttpServletRequest request,
154 IngestService ingestService,
155 List<EventCatalogUIAdapter> eventCatalogUIAdapters,
156 String startDatePattern,
157 String startTimePattern)
158 throws IndexServiceException {
159 EventHttpServletRequest eventHttpServletRequest = new EventHttpServletRequest();
160 try {
161 if (ServletFileUpload.isMultipartContent(request)) {
162 eventHttpServletRequest.setMediaPackage(ingestService.createMediaPackage());
163 if (eventHttpServletRequest.getMediaPackage().isNone()) {
164 throw new IndexServiceException("Unable to create a new mediapackage to store the new event's media.");
165 }
166
167 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
168 FileItemStream item = iter.next();
169 String fieldName = item.getFieldName();
170 if (item.isFormField()) {
171 setFormField(eventCatalogUIAdapters, eventHttpServletRequest, item, fieldName, startDatePattern, 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.toString()));
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.toString()));
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, startTimePattern);
340 }
341 }
342 } catch (IOException e) {
343 throw new IndexServiceException("Unable to update event", e);
344 } catch (FileUploadException e) {
345 throw new IndexServiceException("Unable to update event", e);
346 }
347 } else {
348 throw new IllegalArgumentException("No multipart content");
349 }
350 return eventHttpServletRequest;
351 }
352
353
354
355
356
357
358
359
360
361
362
363 protected static AccessControlList deserializeJsonToAcl(String json, boolean assumeAllow) throws ParseException {
364 JSONParser parser = new JSONParser();
365 JSONArray aclJson = (JSONArray) parser.parse(json);
366 @SuppressWarnings("unchecked")
367 ListIterator<Object> iterator = aclJson.listIterator();
368 JSONObject aceJson;
369 List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>();
370 while (iterator.hasNext()) {
371 aceJson = (JSONObject) iterator.next();
372 String action = aceJson.get(ACTION_JSON_KEY) != null ? aceJson.get(ACTION_JSON_KEY).toString() : "";
373 String allow;
374 if (assumeAllow) {
375 allow = "true";
376 } else {
377 allow = aceJson.get(ALLOW_JSON_KEY) != null ? aceJson.get(ALLOW_JSON_KEY).toString() : "";
378 }
379 String role = aceJson.get(ROLE_JSON_KEY) != null ? aceJson.get(ROLE_JSON_KEY).toString() : "";
380 if (StringUtils.trimToNull(action) != null && StringUtils.trimToNull(allow) != null
381 && StringUtils.trimToNull(role) != null) {
382 AccessControlEntry ace = new AccessControlEntry(role, action, Boolean.parseBoolean(allow));
383 entries.add(ace);
384 } else {
385 throw new IllegalArgumentException(String.format(
386 "One of the access control elements is missing a property. The action was '%s', allow was '%s' and the role was '%s'",
387 action, allow, role));
388 }
389 }
390 return new AccessControlList(entries);
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 protected static MetadataList deserializeMetadataList(
409 String json,
410 List<EventCatalogUIAdapter> catalogAdapters,
411 String startDatePattern,
412 String startTimePattern)
413 throws ParseException, NotFoundException, java.text.ParseException {
414 MetadataList metadataList = new MetadataList();
415 JSONParser parser = new JSONParser();
416 JSONArray jsonCatalogs = (JSONArray) parser.parse(json);
417 for (int i = 0; i < jsonCatalogs.size(); i++) {
418 JSONObject catalog = (JSONObject) jsonCatalogs.get(i);
419 if (catalog.get("flavor") == null || StringUtils.isBlank(catalog.get("flavor").toString())) {
420 throw new IllegalArgumentException(
421 "Unable to create new event as no flavor was given for one of the metadata collections");
422 }
423 String flavorString = catalog.get("flavor").toString();
424 MediaPackageElementFlavor flavor = MediaPackageElementFlavor.parseFlavor(flavorString);
425
426 DublinCoreMetadataCollection collection = null;
427 EventCatalogUIAdapter adapter = null;
428 for (EventCatalogUIAdapter eventCatalogUIAdapter : catalogAdapters) {
429 if (eventCatalogUIAdapter.getFlavor().equals(flavor)) {
430 adapter = eventCatalogUIAdapter;
431 collection = eventCatalogUIAdapter.getRawFields();
432 }
433 }
434
435 if (collection == null) {
436 throw new IllegalArgumentException(
437 String.format("Unable to find an EventCatalogUIAdapter with Flavor '%s'", flavorString));
438 }
439
440 String fieldsJson = catalog.get("fields").toString();
441 if (StringUtils.trimToNull(fieldsJson) != null) {
442 Map<String, String> fields = RequestUtils.getKeyValueMap(fieldsJson);
443 for (String key : fields.keySet()) {
444 if ("subjects".equals(key)) {
445
446 MetadataField field = collection.getOutputFields().get(DublinCore.PROPERTY_SUBJECT.getLocalName());
447 if (field == null) {
448 throw new NotFoundException(String.format(
449 "Cannot find a metadata field with id 'subject' from Catalog with Flavor '%s'.", flavorString));
450 }
451 collection.removeField(field);
452 try {
453 JSONArray subjects = (JSONArray) parser.parse(fields.get(key));
454 collection.addField(MetadataJson
455 .copyWithDifferentJsonValue(field, StringUtils.join(subjects.iterator(), ",")));
456 } catch (ParseException e) {
457 throw new IllegalArgumentException(
458 String.format("Unable to parse the 'subjects' metadata array field because: %s", e.toString()));
459 }
460 } else if ("startDate".equals(key)) {
461
462 MetadataField field = collection.getOutputFields().get(key);
463 if (field == null) {
464 throw new NotFoundException(String.format(
465 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
466 }
467 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startDatePattern == null ? field.getPattern() : startDatePattern);
468 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
469 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
470 if (field.getValue() != null) {
471 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
472 newStartDate = oldStartDate.withDate(newStartDate.year().get(), newStartDate.monthOfYear().get(), newStartDate.dayOfMonth().get());
473 }
474 collection.removeField(field);
475 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
476 } else if ("startTime".equals(key)) {
477
478 MetadataField field = collection.getOutputFields().get("startDate");
479 if (field == null) {
480 throw new NotFoundException(String.format(
481 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", "startDate", flavorString));
482 }
483 SimpleDateFormat apiSdf = MetadataField.getSimpleDateFormatter(startTimePattern == null ? "HH:mm" : startTimePattern);
484 SimpleDateFormat sdf = MetadataField.getSimpleDateFormatter(field.getPattern());
485 DateTime newStartDate = new DateTime(apiSdf.parse(fields.get(key)), DateTimeZone.UTC);
486 if (field.getValue() != null) {
487 DateTime oldStartDate = new DateTime(sdf.parse((String) field.getValue()), DateTimeZone.UTC);
488 newStartDate = oldStartDate.withTime(
489 newStartDate.hourOfDay().get(),
490 newStartDate.minuteOfHour().get(),
491 newStartDate.secondOfMinute().get(),
492 newStartDate.millisOfSecond().get());
493 }
494 collection.removeField(field);
495 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, sdf.format(newStartDate.toDate())));
496 } else {
497 MetadataField field = collection.getOutputFields().get(key);
498 if (field == null) {
499 throw new NotFoundException(String.format(
500 "Cannot find a metadata field with id '%s' from Catalog with Flavor '%s'.", key, flavorString));
501 }
502 collection.removeField(field);
503 collection.addField(MetadataJson.copyWithDifferentJsonValue(field, fields.get(key)));
504 }
505 }
506 }
507 metadataList.add(adapter, collection);
508 }
509 setStartDateAndTimeIfUnset(metadataList);
510 return metadataList;
511 }
512
513
514
515
516
517
518
519 private static void setStartDateAndTimeIfUnset(MetadataList metadataList) {
520 final DublinCoreMetadataCollection commonEventCollection = metadataList
521 .getMetadataByFlavor(MediaPackageElements.EPISODE.toString());
522 if (commonEventCollection != null) {
523 MetadataField startDate = commonEventCollection.getOutputFields().get("startDate");
524 if (!startDate.isUpdated()) {
525 SimpleDateFormat utcDateFormat = new SimpleDateFormat(startDate.getPattern());
526 utcDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
527 String currentDate = utcDateFormat.format(new DateTime(DateTimeZone.UTC).toDate());
528 commonEventCollection.removeField(startDate);
529 commonEventCollection.addField(MetadataJson.copyWithDifferentJsonValue(startDate, currentDate));
530 }
531 }
532 }
533 }