1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.opencastproject.mediapackage;
24
25 import static org.opencastproject.mediapackage.MediaPackageSupport.Filters.presentations;
26 import static org.opencastproject.util.data.Monadics.mlist;
27
28 import org.opencastproject.mediapackage.MediaPackageElement.Type;
29 import org.opencastproject.mediapackage.identifier.Id;
30 import org.opencastproject.mediapackage.identifier.IdImpl;
31 import org.opencastproject.util.DateTimeSupport;
32 import org.opencastproject.util.IoSupport;
33 import org.opencastproject.util.XmlSafeParser;
34
35 import org.apache.commons.io.IOUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.w3c.dom.Node;
40 import org.w3c.dom.bootstrap.DOMImplementationRegistry;
41 import org.w3c.dom.ls.DOMImplementationLS;
42 import org.w3c.dom.ls.LSOutput;
43 import org.w3c.dom.ls.LSSerializer;
44 import org.xml.sax.SAXException;
45
46 import java.io.ByteArrayInputStream;
47 import java.io.ByteArrayOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.net.URI;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collection;
54 import java.util.Date;
55 import java.util.HashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.TreeSet;
60 import java.util.UUID;
61
62 import javax.xml.bind.JAXBContext;
63 import javax.xml.bind.JAXBException;
64 import javax.xml.bind.Unmarshaller;
65 import javax.xml.bind.annotation.XmlAccessType;
66 import javax.xml.bind.annotation.XmlAccessorType;
67 import javax.xml.bind.annotation.XmlAttribute;
68 import javax.xml.bind.annotation.XmlElement;
69 import javax.xml.bind.annotation.XmlElementWrapper;
70 import javax.xml.bind.annotation.XmlRootElement;
71 import javax.xml.bind.annotation.XmlType;
72 import javax.xml.bind.annotation.adapters.XmlAdapter;
73 import javax.xml.transform.stream.StreamSource;
74
75
76
77
78 @XmlType(name = "mediapackage", namespace = "http://mediapackage.opencastproject.org", propOrder = { "title", "series",
79 "seriesTitle", "creators", "contributors", "subjects", "license", "language", "tracks", "catalogs",
80 "attachments", "publications" })
81 @XmlRootElement(name = "mediapackage", namespace = "http://mediapackage.opencastproject.org")
82 @XmlAccessorType(XmlAccessType.NONE)
83 public final class MediaPackageImpl implements MediaPackage {
84
85
86 private static final Logger logger = LoggerFactory.getLogger(MediaPackageImpl.class.getName());
87
88
89
90
91
92 public static final String NEGATE_TAG_PREFIX = "-";
93
94
95 static final JAXBContext context;
96
97
98 private MediaPackageElementBuilder mediaPackageElementBuilder = null;
99
100 @XmlElement(name = "title")
101 private String title = null;
102
103 @XmlElement(name = "seriestitle")
104 private String seriesTitle = null;
105
106 @XmlElement(name = "language")
107 private String language = null;
108
109 @XmlElement(name = "series")
110 private String series = null;
111
112 @XmlElement(name = "license")
113 private String license = null;
114
115 @XmlElementWrapper(name = "creators")
116 @XmlElement(name = "creator")
117 private Set<String> creators = null;
118
119 @XmlElementWrapper(name = "contributors")
120 @XmlElement(name = "contributor")
121 private Set<String> contributors = null;
122
123 @XmlElementWrapper(name = "subjects")
124 @XmlElement(name = "subject")
125 private Set<String> subjects = null;
126
127
128 private Id identifier = null;
129
130
131 private long startTime = 0L;
132
133
134 private Long duration = null;
135
136
137 private final List<MediaPackageElement> elements = new ArrayList<MediaPackageElement>();
138
139
140 private int tracks = 0;
141
142
143 private int catalogs = 0;
144
145
146 private int attachments = 0;
147
148
149 private int others = 0;
150
151 static {
152 try {
153 context = JAXBContext.newInstance("org.opencastproject.mediapackage", MediaPackageImpl.class.getClassLoader());
154 } catch (JAXBException e) {
155 throw new RuntimeException(e);
156 }
157 }
158
159
160
161
162 MediaPackageImpl() {
163 this(IdImpl.fromUUID());
164 }
165
166
167
168
169
170
171
172 MediaPackageImpl(Id id) {
173 this.identifier = id;
174 }
175
176
177
178
179
180
181 @XmlAttribute(name = "id")
182 @Override
183 public Id getIdentifier() {
184 return identifier;
185 }
186
187
188
189
190
191
192 @Override
193 public void setIdentifier(Id identifier) {
194 this.identifier = identifier;
195 }
196
197
198
199
200
201
202 @XmlAttribute(name = "duration")
203 @Override
204 public Long getDuration() {
205 if (duration == null && hasTracks()) {
206 recalculateDuration();
207 }
208 return duration;
209 }
210
211
212
213
214 private void recalculateDuration() {
215
216 duration = null;
217 for (Track t : getTracks()) {
218 if (t.getDuration() != null) {
219 if (duration == null || duration < t.getDuration())
220 duration = t.getDuration();
221 }
222 }
223 }
224
225
226
227
228
229
230 @Override
231 public void setDuration(Long duration) throws IllegalStateException {
232 if (hasTracks())
233 throw new IllegalStateException(
234 "The duration is determined by the length of the tracks and cannot be set manually");
235 this.duration = duration;
236 }
237
238
239
240
241
242
243 @Override
244 public Date getDate() {
245 return new Date(startTime);
246 }
247
248
249
250
251
252
253 @XmlAttribute(name = "start")
254 public String getStartDateAsString() {
255 if (startTime == 0)
256 return null;
257 return DateTimeSupport.toUTC(startTime);
258 }
259
260
261
262
263
264
265
266 public void setStartDateAsString(String startTime) {
267 if (startTime != null && !"0".equals(startTime) && !startTime.isEmpty()) {
268 try {
269 this.startTime = DateTimeSupport.fromUTC(startTime);
270 } catch (Exception e) {
271 logger.info("Unable to parse start time {}", startTime);
272 }
273 } else {
274 this.startTime = 0;
275 }
276 }
277
278
279
280
281
282
283 @Override
284 public Iterable<MediaPackageElement> elements() {
285 return Arrays.asList(getElements());
286 }
287
288
289
290
291
292
293 @Override
294 public MediaPackageElement[] getElements() {
295 return elements.toArray(new MediaPackageElement[elements.size()]);
296 }
297
298
299
300
301
302
303 @Override
304 public MediaPackageElement getElementByReference(MediaPackageReference reference) {
305 for (MediaPackageElement e : this.elements) {
306 if (!reference.getType().equalsIgnoreCase(e.getElementType().toString()))
307 continue;
308 if (reference.getIdentifier().equals(e.getIdentifier()))
309 return e;
310 }
311 return null;
312 }
313
314
315
316
317 @Override
318 public MediaPackageElement getElementById(String id) {
319 for (MediaPackageElement element : getElements()) {
320 if (id.equals(element.getIdentifier()))
321 return element;
322 }
323 return null;
324 }
325
326
327
328
329
330
331 @Override
332 public MediaPackageElement[] getElementsByTags(Collection<String> tags) {
333 if (tags == null || tags.isEmpty())
334 return getElements();
335 Set<String> keep = new HashSet<String>();
336 Set<String> lose = new HashSet<String>();
337 for (String tag : tags) {
338 if (StringUtils.isBlank(tag))
339 continue;
340 if (tag.startsWith(NEGATE_TAG_PREFIX)) {
341 lose.add(tag.substring(NEGATE_TAG_PREFIX.length()));
342 } else {
343 keep.add(tag);
344 }
345 }
346 List<MediaPackageElement> result = new ArrayList<>();
347 for (MediaPackageElement element : getElements()) {
348 boolean add = false;
349 for (String elementTag : element.getTags()) {
350 if (lose.contains(elementTag)) {
351 add = false;
352 break;
353 } else if (keep.contains(elementTag)) {
354 add = true;
355 }
356 }
357 if (add) {
358 result.add(element);
359 }
360 }
361 return result.toArray(new MediaPackageElement[result.size()]);
362 }
363
364
365
366
367
368
369 @Override
370 public Catalog[] getCatalogsByTags(Collection<String> tags) {
371 MediaPackageElement[] matchingElements = getElementsByTags(tags);
372 List<Catalog> catalogs = new ArrayList<>();
373 for (MediaPackageElement element : matchingElements) {
374 if (Catalog.TYPE.equals(element.getElementType())) {
375 catalogs.add((Catalog) element);
376 }
377 }
378 return catalogs.toArray(new Catalog[catalogs.size()]);
379 }
380
381
382
383
384
385
386 @Override
387 public Track[] getTracksByTags(Collection<String> tags) {
388 MediaPackageElement[] matchingElements = getElementsByTags(tags);
389 List<Track> tracks = new ArrayList<>();
390 for (MediaPackageElement element : matchingElements) {
391 if (Track.TYPE.equals(element.getElementType())) {
392 tracks.add((Track) element);
393 }
394 }
395 return tracks.toArray(new Track[tracks.size()]);
396 }
397
398
399
400
401
402
403 @Override
404 public MediaPackageElement[] getElementsByFlavor(MediaPackageElementFlavor flavor) {
405 if (flavor == null)
406 throw new IllegalArgumentException("Flavor cannot be null");
407
408 List<MediaPackageElement> elements = new ArrayList<>();
409 for (MediaPackageElement element : getElements()) {
410 if (flavor.matches(element.getFlavor()))
411 elements.add(element);
412 }
413 return elements.toArray(new MediaPackageElement[elements.size()]);
414 }
415
416
417
418
419 @Override
420 public boolean contains(MediaPackageElement element) {
421 if (element == null)
422 throw new IllegalArgumentException("Media package element must not be null");
423 return (elements.contains(element));
424 }
425
426
427
428
429
430
431
432
433 boolean contains(String identifier) {
434 for (MediaPackageElement element : getElements()) {
435 if (element.getIdentifier().equals(identifier))
436 return true;
437 }
438 return false;
439 }
440
441
442
443
444 @Override
445 public void add(Catalog catalog) {
446 integrateCatalog(catalog);
447 addInternal(catalog);
448 }
449
450
451
452
453 @Override
454 public void add(Track track) {
455 integrateTrack(track);
456 addInternal(track);
457 }
458
459
460
461
462 @Override
463 public void add(Attachment attachment) {
464 integrateAttachment(attachment);
465 addInternal(attachment);
466 }
467
468
469
470
471 @Override
472 public Catalog getCatalog(String catalogId) {
473 synchronized (elements) {
474 for (MediaPackageElement e : elements) {
475 if (e.getIdentifier().equals(catalogId) && e instanceof Catalog)
476 return (Catalog) e;
477 }
478 }
479 return null;
480 }
481
482
483
484
485 @XmlElementWrapper(name = "metadata")
486 @XmlElement(name = "catalog")
487 @Override
488 public Catalog[] getCatalogs() {
489 Collection<Catalog> catalogs = loadCatalogs();
490 return catalogs.toArray(new Catalog[catalogs.size()]);
491 }
492
493 void setCatalogs(Catalog[] catalogs) {
494 List<Catalog> newCatalogs = Arrays.asList(catalogs);
495 List<Catalog> oldCatalogs = Arrays.asList(getCatalogs());
496
497 for (Catalog existing : oldCatalogs) {
498 if (!newCatalogs.contains(existing)) {
499 remove(existing);
500 }
501 }
502 for (Catalog newCatalog : newCatalogs) {
503 if (!oldCatalogs.contains(newCatalog)) {
504 add(newCatalog);
505 }
506 }
507 }
508
509
510
511
512 @Override
513 public Catalog[] getCatalogs(MediaPackageElementFlavor flavor) {
514 if (flavor == null)
515 throw new IllegalArgumentException("Unable to filter by null criterion");
516
517
518 Collection<Catalog> catalogs = loadCatalogs();
519 List<Catalog> candidates = new ArrayList<>(catalogs);
520 for (Catalog c : catalogs) {
521 if (c.getFlavor() == null || !c.getFlavor().matches(flavor)) {
522 candidates.remove(c);
523 }
524 }
525 return candidates.toArray(new Catalog[0]);
526 }
527
528
529
530
531 @Override
532 public Catalog[] getCatalogs(MediaPackageReference reference) {
533 return getCatalogs(reference, false);
534 }
535
536 private Catalog[] getCatalogs(MediaPackageReference reference, boolean includeDerived) {
537 if (reference == null)
538 throw new IllegalArgumentException("Unable to filter by null reference");
539
540
541 Collection<Catalog> catalogs = loadCatalogs();
542 List<Catalog> candidates = new ArrayList<>(catalogs);
543 for (Catalog c : catalogs) {
544 MediaPackageReference r = c.getReference();
545 if (!reference.matches(r)) {
546 boolean indirectHit = false;
547
548
549 MediaPackageReference elementRef = new MediaPackageReferenceImpl(reference.getType(), reference.getIdentifier());
550
551
552 while (includeDerived && r != null) {
553 if (r.matches(elementRef)) {
554 indirectHit = true;
555 break;
556 }
557 r = getElement(r).getReference();
558 }
559
560 if (!indirectHit)
561 candidates.remove(c);
562 }
563 }
564
565 return candidates.toArray(new Catalog[candidates.size()]);
566 }
567
568
569
570
571
572 @Override
573 public Catalog[] getCatalogs(MediaPackageElementFlavor flavor, MediaPackageReference reference) {
574 if (flavor == null)
575 throw new IllegalArgumentException("Unable to filter by null criterion");
576 if (reference == null)
577 throw new IllegalArgumentException("Unable to filter by null reference");
578
579
580 Collection<Catalog> catalogs = loadCatalogs();
581 List<Catalog> candidates = new ArrayList<>(catalogs);
582 for (Catalog c : catalogs) {
583 if (!flavor.equals(c.getFlavor()) || (c.getReference() != null && !c.getReference().matches(reference))) {
584 candidates.remove(c);
585 }
586 }
587 return candidates.toArray(new Catalog[candidates.size()]);
588 }
589
590
591
592
593
594
595 @Override
596 public Track getTrack(String trackId) {
597 synchronized (elements) {
598 for (MediaPackageElement e : elements) {
599 if (e.getIdentifier().equals(trackId) && e instanceof Track)
600 return (Track) e;
601 }
602 }
603 return null;
604 }
605
606
607
608
609
610
611 @XmlElementWrapper(name = "media")
612 @XmlElement(name = "track")
613 @Override
614 public Track[] getTracks() {
615 Collection<Track> tracks = loadTracks();
616 return tracks.toArray(new Track[tracks.size()]);
617 }
618
619 void setTracks(Track[] tracks) {
620 List<Track> newTracks = Arrays.asList(tracks);
621 List<Track> oldTracks = Arrays.asList(getTracks());
622
623 for (Track existing : oldTracks) {
624 if (!newTracks.contains(existing)) {
625 remove(existing);
626 }
627 }
628 for (Track newTrack : newTracks) {
629 if (!oldTracks.contains(newTrack)) {
630 add(newTrack);
631 }
632 }
633 }
634
635
636
637
638
639
640 @Override
641 public Track[] getTracksByTag(String tag) {
642 List<Track> result = new ArrayList<>();
643 synchronized (elements) {
644 for (MediaPackageElement e : elements) {
645 if (e instanceof Track && e.containsTag(tag))
646 result.add((Track) e);
647 }
648 }
649 return result.toArray(new Track[result.size()]);
650 }
651
652
653
654
655
656
657 @Override
658 public Track[] getTracks(MediaPackageElementFlavor flavor) {
659 if (flavor == null)
660 throw new IllegalArgumentException("Unable to filter by null criterion");
661
662
663 Collection<Track> tracks = loadTracks();
664 List<Track> candidates = new ArrayList<>(tracks);
665 for (Track a : tracks) {
666 if (a.getFlavor() == null || !a.getFlavor().matches(flavor)) {
667 candidates.remove(a);
668 }
669 }
670 return candidates.toArray(new Track[candidates.size()]);
671 }
672
673
674
675
676
677
678 @Override
679 public boolean hasTracks() {
680 synchronized (elements) {
681 for (MediaPackageElement e : elements) {
682 if (e instanceof Track)
683 return true;
684 }
685 }
686 return false;
687 }
688
689
690
691
692
693
694 @Override
695 public MediaPackageElement[] getUnclassifiedElements() {
696 return getUnclassifiedElements(null);
697 }
698
699 private MediaPackageElement[] getUnclassifiedElements(MediaPackageElementFlavor flavor) {
700 List<MediaPackageElement> unclassifieds = new ArrayList<>();
701 synchronized (elements) {
702 for (MediaPackageElement e : elements) {
703 if (!(e instanceof Attachment) && !(e instanceof Catalog) && !(e instanceof Track)) {
704 if (flavor == null || flavor.equals(e.getFlavor())) {
705 unclassifieds.add(e);
706 }
707 }
708 }
709 }
710 return unclassifieds.toArray(new MediaPackageElement[unclassifieds.size()]);
711 }
712
713
714
715
716
717
718 @Override
719 public Attachment getAttachment(String attachmentId) {
720 synchronized (elements) {
721 for (MediaPackageElement e : elements) {
722 if (e.getIdentifier().equals(attachmentId) && e instanceof Attachment)
723 return (Attachment) e;
724 }
725 }
726 return null;
727 }
728
729
730
731
732
733
734 @XmlElementWrapper(name = "attachments")
735 @XmlElement(name = "attachment")
736 @Override
737 public Attachment[] getAttachments() {
738 Collection<Attachment> attachments = loadAttachments();
739 return attachments.toArray(new Attachment[attachments.size()]);
740 }
741
742 void setAttachments(Attachment[] catalogs) {
743 List<Attachment> newAttachments = Arrays.asList(catalogs);
744 List<Attachment> oldAttachments = Arrays.asList(getAttachments());
745
746 for (Attachment existing : oldAttachments) {
747 if (!newAttachments.contains(existing)) {
748 remove(existing);
749 }
750 }
751 for (Attachment newAttachment : newAttachments) {
752 if (!oldAttachments.contains(newAttachment)) {
753 add(newAttachment);
754 }
755 }
756 }
757
758
759
760
761
762
763 @Override
764 public Attachment[] getAttachments(MediaPackageElementFlavor flavor) {
765 if (flavor == null)
766 throw new IllegalArgumentException("Unable to filter by null criterion");
767
768
769 Collection<Attachment> attachments = loadAttachments();
770 List<Attachment> candidates = new ArrayList<>(attachments);
771 for (Attachment a : attachments) {
772 if (a.getFlavor() == null || !a.getFlavor().matches(flavor)) {
773 candidates.remove(a);
774 }
775 }
776 return candidates.toArray(new Attachment[candidates.size()]);
777 }
778
779
780
781
782
783
784 @XmlElementWrapper(name = "publications")
785 @XmlElement(name = "publication")
786 @Override
787 public Publication[] getPublications() {
788 return mlist(elements).bind(presentations).value().toArray(new Publication[0]);
789 }
790
791 void setPublications(Publication[] publications) {
792 List<Publication> newPublications = Arrays.asList(publications);
793 List<Publication> oldPublications = Arrays.asList(getPublications());
794 for (Publication oldp : oldPublications) {
795 if (!newPublications.contains(oldp)) {
796 remove(oldp);
797 }
798 }
799 for (Publication newp : newPublications) {
800 if (!oldPublications.contains(newp)) {
801 add(newp);
802 }
803 }
804 }
805
806
807
808
809
810
811 @Override
812 public MediaPackageElement removeElementById(String id) {
813 MediaPackageElement element = getElementById(id);
814 if (element == null)
815 return null;
816 remove(element);
817 return element;
818 }
819
820
821
822
823
824
825 @Override
826 public void remove(MediaPackageElement element) {
827 removeElement(element);
828 }
829
830
831
832
833
834
835 @Override
836 public void remove(Attachment attachment) {
837 removeElement(attachment);
838 }
839
840
841
842
843
844
845 @Override
846 public void remove(Catalog catalog) {
847 removeElement(catalog);
848 }
849
850
851
852
853
854
855 @Override
856 public void remove(Track track) {
857 removeElement(track);
858 recalculateDuration();
859 }
860
861
862
863
864
865
866
867 void removeElement(MediaPackageElement element) {
868 removeInternal(element);
869 if (element instanceof AbstractMediaPackageElement) {
870 ((AbstractMediaPackageElement) element).setMediaPackage(null);
871 }
872 }
873
874
875
876
877
878
879 @Override
880 public MediaPackageElement add(URI url) {
881 if (url == null)
882 throw new IllegalArgumentException("Argument 'url' may not be null");
883
884 if (mediaPackageElementBuilder == null) {
885 mediaPackageElementBuilder = MediaPackageElementBuilderFactory.newInstance().newElementBuilder();
886 }
887 MediaPackageElement element = mediaPackageElementBuilder.elementFromURI(url);
888 integrate(element);
889 addInternal(element);
890 return element;
891 }
892
893
894
895
896
897
898 @Override
899 public MediaPackageElement add(URI uri, Type type, MediaPackageElementFlavor flavor) {
900 if (uri == null)
901 throw new IllegalArgumentException("Argument 'url' may not be null");
902 if (type == null)
903 throw new IllegalArgumentException("Argument 'type' may not be null");
904
905 if (mediaPackageElementBuilder == null) {
906 mediaPackageElementBuilder = MediaPackageElementBuilderFactory.newInstance().newElementBuilder();
907 }
908 MediaPackageElement element = mediaPackageElementBuilder.elementFromURI(uri, type, flavor);
909 integrate(element);
910 addInternal(element);
911 return element;
912 }
913
914
915
916
917
918
919 @Override
920 public void add(MediaPackageElement element) {
921 if (element.getElementType().equals(MediaPackageElement.Type.Track) && element instanceof Track) {
922 integrateTrack((Track) element);
923 } else if (element.getElementType().equals(MediaPackageElement.Type.Catalog) && element instanceof Catalog) {
924 integrateCatalog((Catalog) element);
925 } else if (element.getElementType().equals(MediaPackageElement.Type.Attachment) && element instanceof Attachment) {
926 integrateAttachment((Attachment) element);
927 } else {
928 integrate(element);
929 }
930 addInternal(element);
931 }
932
933
934
935
936
937
938
939 @Override
940 public void addDerived(MediaPackageElement derivedElement, MediaPackageElement sourceElement) {
941 addDerived(derivedElement, sourceElement, null);
942 }
943
944 private void addDerived(
945 MediaPackageElement derivedElement, MediaPackageElement sourceElement, Map<String, String> properties) {
946 if (derivedElement == null)
947 throw new IllegalArgumentException("The derived element is null");
948 if (sourceElement == null)
949 throw new IllegalArgumentException("The source element is null");
950 if (!contains(sourceElement))
951 throw new IllegalStateException("The sourceElement needs to be part of the media package");
952
953 derivedElement.referTo(sourceElement);
954 addInternal(derivedElement);
955
956 if (properties != null) {
957 MediaPackageReference ref = derivedElement.getReference();
958 for (Map.Entry<String, String> entry : properties.entrySet()) {
959 ref.setProperty(entry.getKey(), entry.getValue());
960 }
961 }
962 }
963
964
965
966
967
968
969
970 @Override
971 public MediaPackageElement[] getDerived(MediaPackageElement sourceElement, MediaPackageElementFlavor derivateFlavor) {
972 if (sourceElement == null)
973 throw new IllegalArgumentException("Source element cannot be null");
974 if (derivateFlavor == null)
975 throw new IllegalArgumentException("Derivate flavor cannot be null");
976
977 MediaPackageReference reference = new MediaPackageReferenceImpl(sourceElement);
978 List<MediaPackageElement> elements = new ArrayList<>();
979 for (MediaPackageElement element : getElements()) {
980 if (derivateFlavor.equals(element.getFlavor()) && reference.equals(element.getReference()))
981 elements.add(element);
982 }
983 return elements.toArray(new MediaPackageElement[elements.size()]);
984 }
985
986
987
988
989
990
991
992
993 private void integrate(MediaPackageElement element) {
994 if (element instanceof AbstractMediaPackageElement)
995 ((AbstractMediaPackageElement) element).setMediaPackage(this);
996 }
997
998
999
1000
1001
1002
1003
1004
1005 private void integrateCatalog(Catalog catalog) {
1006
1007 String id = catalog.getIdentifier();
1008 if (id == null || contains(id)) {
1009 catalog.setIdentifier(createElementIdentifier());
1010 }
1011 integrate(catalog);
1012 }
1013
1014
1015
1016
1017
1018
1019
1020
1021 private void integrateTrack(Track track) {
1022
1023 String id = track.getIdentifier();
1024 if (id == null || contains(id)) {
1025 track.setIdentifier(createElementIdentifier());
1026 }
1027 integrate(track);
1028 }
1029
1030
1031
1032
1033
1034
1035
1036
1037 private void integrateAttachment(Attachment attachment) {
1038
1039 String id = attachment.getIdentifier();
1040 if (id == null || contains(id)) {
1041 attachment.setIdentifier(createElementIdentifier());
1042 }
1043 integrate(attachment);
1044 }
1045
1046
1047
1048
1049
1050
1051 private String createElementIdentifier() {
1052 return UUID.randomUUID().toString();
1053 }
1054
1055
1056
1057
1058 @Override
1059 public void verify() throws MediaPackageException {
1060 for (MediaPackageElement e : getElements()) {
1061 e.verify();
1062 }
1063 }
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076 public static MediaPackageImpl valueOf(String xml) throws MediaPackageException {
1077 return MediaPackageImpl.valueOf(IOUtils.toInputStream(xml, "UTF-8"));
1078 }
1079
1080
1081
1082
1083
1084 @Override
1085 public int hashCode() {
1086 return getIdentifier().hashCode();
1087 }
1088
1089
1090
1091
1092 @Override
1093 public boolean equals(Object obj) {
1094 if (obj instanceof MediaPackage) {
1095 return getIdentifier().equals(((MediaPackage) obj).getIdentifier());
1096 }
1097 return false;
1098 }
1099
1100
1101
1102
1103
1104
1105 @Override
1106 public Object clone() {
1107 try {
1108 String xml = MediaPackageParser.getAsXml(this);
1109 return MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().loadFromXml(xml);
1110 } catch (Exception e) {
1111 throw new RuntimeException(e);
1112 }
1113 }
1114
1115
1116
1117
1118 @Override
1119 public String toString() {
1120 if (identifier != null)
1121 return identifier.toString();
1122 else
1123 return "Unknown media package";
1124 }
1125
1126
1127
1128
1129 public static class Adapter extends XmlAdapter<MediaPackageImpl, MediaPackage> {
1130 @Override
1131 public MediaPackageImpl marshal(MediaPackage mp) throws Exception {
1132 return (MediaPackageImpl) mp;
1133 }
1134
1135 @Override
1136 public MediaPackage unmarshal(MediaPackageImpl mp) throws Exception {
1137 return mp;
1138 }
1139 }
1140
1141
1142
1143
1144
1145
1146
1147
1148 public static MediaPackageImpl valueOf(InputStream xml) throws MediaPackageException {
1149 try {
1150 Unmarshaller unmarshaller = context.createUnmarshaller();
1151 return unmarshaller.unmarshal(XmlSafeParser.parse(xml), MediaPackageImpl.class).getValue();
1152 } catch (JAXBException e) {
1153 throw new MediaPackageException(e.getLinkedException() != null ? e.getLinkedException() : e);
1154 } catch (IOException | SAXException e) {
1155 throw new MediaPackageException(e);
1156 } finally {
1157 IoSupport.closeQuietly(xml);
1158 }
1159 }
1160
1161
1162
1163
1164
1165
1166
1167
1168 public static MediaPackageImpl valueOf(Node xml) throws MediaPackageException {
1169 try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
1170 Unmarshaller unmarshaller = context.createUnmarshaller();
1171
1172 DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
1173 DOMImplementationLS impl = (DOMImplementationLS) reg.getDOMImplementation("LS");
1174 LSSerializer serializer = impl.createLSSerializer();
1175 serializer.getDomConfig().setParameter("comments", false);
1176 serializer.getDomConfig().setParameter("format-pretty-print", false);
1177 LSOutput output = impl.createLSOutput();
1178 output.setEncoding("UTF-8");
1179 output.setByteStream(out);
1180
1181 serializer.write(xml, output);
1182
1183 try (InputStream in = new ByteArrayInputStream(out.toByteArray())) {
1184
1185
1186 return unmarshaller.unmarshal(new StreamSource(in), MediaPackageImpl.class).getValue();
1187
1188 }
1189 } catch (Exception e) {
1190 throw new MediaPackageException("Error deserializing media package node", e);
1191 }
1192 }
1193
1194
1195
1196
1197
1198
1199 @Override
1200 public String[] getContributors() {
1201 if (contributors == null)
1202 return new String[] {};
1203 return contributors.toArray(new String[contributors.size()]);
1204 }
1205
1206
1207
1208
1209
1210
1211 @Override
1212 public String[] getCreators() {
1213 if (creators == null)
1214 return new String[] {};
1215 return creators.toArray(new String[creators.size()]);
1216 }
1217
1218
1219
1220
1221
1222
1223 @Override
1224 public String getLanguage() {
1225 return language;
1226 }
1227
1228
1229
1230
1231
1232
1233 @Override
1234 public String getLicense() {
1235 return license;
1236 }
1237
1238
1239
1240
1241
1242
1243 @Override
1244 public String getSeries() {
1245 return series;
1246 }
1247
1248
1249
1250
1251
1252
1253 @Override
1254 public String[] getSubjects() {
1255 if (subjects == null)
1256 return new String[] {};
1257 return subjects.toArray(new String[subjects.size()]);
1258 }
1259
1260
1261
1262
1263
1264
1265 @Override
1266 public String getTitle() {
1267 return title;
1268 }
1269
1270
1271
1272
1273
1274
1275 @Override
1276 public String getSeriesTitle() {
1277 return seriesTitle;
1278 }
1279
1280
1281
1282
1283
1284
1285 @Override
1286 public void setSeriesTitle(String seriesTitle) {
1287 this.seriesTitle = seriesTitle;
1288 }
1289
1290
1291
1292
1293
1294
1295 @Override
1296 public void addContributor(String contributor) {
1297 if (contributors == null)
1298 contributors = new TreeSet<String>();
1299 contributors.add(contributor);
1300 }
1301
1302
1303
1304
1305
1306
1307 @Override
1308 public void addCreator(String creator) {
1309 if (creators == null)
1310 creators = new TreeSet<>();
1311 creators.add(creator);
1312 }
1313
1314
1315
1316
1317
1318
1319 @Override
1320 public void addSubject(String subject) {
1321 if (subjects == null)
1322 subjects = new TreeSet<>();
1323 subjects.add(subject);
1324 }
1325
1326
1327
1328
1329
1330
1331 @Override
1332 public void removeContributor(String contributor) {
1333 if (contributors != null)
1334 contributors.remove(contributor);
1335 }
1336
1337
1338
1339
1340
1341
1342 @Override
1343 public void removeCreator(String creator) {
1344 if (creators != null)
1345 creators.remove(creator);
1346 }
1347
1348
1349
1350
1351
1352
1353 @Override
1354 public void removeSubject(String subject) {
1355 if (subjects != null)
1356 subjects.remove(subject);
1357 }
1358
1359
1360
1361
1362
1363
1364 @Override
1365 public void setDate(Date date) {
1366 if (date != null)
1367 this.startTime = date.getTime();
1368 else
1369 this.startTime = 0;
1370 }
1371
1372
1373
1374
1375
1376
1377 @Override
1378 public void setLanguage(String language) {
1379 this.language = language;
1380 }
1381
1382
1383
1384
1385
1386
1387 @Override
1388 public void setLicense(String license) {
1389 this.license = license;
1390 }
1391
1392
1393
1394
1395
1396
1397 @Override
1398 public void setSeries(String identifier) {
1399 this.series = identifier;
1400 }
1401
1402
1403
1404
1405
1406
1407 @Override
1408 public void setTitle(String title) {
1409 this.title = title;
1410 }
1411
1412 @Override
1413 public boolean isLive() {
1414 return Arrays.stream(getTracks()).anyMatch(Track::isLive);
1415 }
1416
1417
1418
1419
1420
1421
1422
1423
1424 MediaPackageElement getElement(MediaPackageReference reference) {
1425 if (reference == null)
1426 return null;
1427 for (MediaPackageElement e : elements) {
1428 if (e.getIdentifier().equals(reference.getIdentifier()))
1429 return e;
1430 }
1431 return null;
1432 }
1433
1434
1435
1436
1437
1438
1439
1440 private void addInternal(MediaPackageElement element) {
1441 if (element == null)
1442 throw new IllegalArgumentException("Media package element must not be null");
1443
1444 elements.add(element);
1445 if (element instanceof Track) {
1446 recalculateDuration();
1447 }
1448
1449
1450 if (element.getIdentifier() == null) {
1451 if (element instanceof AbstractMediaPackageElement) {
1452 element.setIdentifier(createElementIdentifier());
1453 } else
1454 throw new UnsupportedElementException(element, "Found unknown element without id");
1455 }
1456 }
1457
1458
1459
1460
1461
1462
1463
1464 void removeInternal(MediaPackageElement element) {
1465 if (element == null)
1466 throw new IllegalArgumentException("Media package element must not be null");
1467
1468 if (elements.remove(element) && element instanceof Track) {
1469 recalculateDuration();
1470 }
1471 }
1472
1473
1474
1475
1476
1477
1478 private Collection<Track> loadTracks() {
1479 List<Track> tracks = new ArrayList<>();
1480 synchronized (elements) {
1481 for (MediaPackageElement e : elements) {
1482 if (e instanceof Track) {
1483 tracks.add((Track) e);
1484 }
1485 }
1486 }
1487 return tracks;
1488 }
1489
1490
1491
1492
1493
1494
1495 private Collection<Catalog> loadCatalogs() {
1496 List<Catalog> catalogs = new ArrayList<>();
1497 synchronized (elements) {
1498 for (MediaPackageElement e : elements) {
1499 if (e instanceof Catalog) {
1500 catalogs.add((Catalog) e);
1501 }
1502 }
1503 }
1504 return catalogs;
1505 }
1506
1507
1508
1509
1510
1511
1512 private Collection<Attachment> loadAttachments() {
1513 List<Attachment> attachments = new ArrayList<>();
1514 synchronized (elements) {
1515 for (MediaPackageElement e : elements) {
1516 if (e instanceof Attachment) {
1517 attachments.add((Attachment) e);
1518 }
1519 }
1520 }
1521 return attachments;
1522 }
1523 }