View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.mediapackage;
23  
24  import org.opencastproject.util.Checksum;
25  import org.opencastproject.util.IoSupport;
26  import org.opencastproject.util.MimeType;
27  
28  import org.w3c.dom.Document;
29  import org.w3c.dom.Element;
30  import org.w3c.dom.Node;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.Serializable;
35  import java.net.URI;
36  import java.net.URISyntaxException;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.SortedSet;
40  import java.util.TreeSet;
41  import java.util.UUID;
42  
43  import javax.xml.bind.JAXBException;
44  import javax.xml.bind.Marshaller;
45  import javax.xml.bind.Unmarshaller;
46  import javax.xml.bind.annotation.XmlAccessType;
47  import javax.xml.bind.annotation.XmlAccessorType;
48  import javax.xml.bind.annotation.XmlAttribute;
49  import javax.xml.bind.annotation.XmlElement;
50  import javax.xml.bind.annotation.XmlElementWrapper;
51  import javax.xml.bind.annotation.XmlID;
52  import javax.xml.bind.annotation.XmlTransient;
53  
54  /**
55   * This class provides base functionality for media package elements.
56   */
57  @XmlTransient
58  @XmlAccessorType(XmlAccessType.NONE)
59  public abstract class AbstractMediaPackageElement implements MediaPackageElement, Serializable {
60  
61    /** Serial version uid */
62    private static final long serialVersionUID = 1L;
63  
64    /** The element identifier */
65    @XmlID
66    @XmlAttribute(name = "id")
67    protected String id = null;
68  
69    /** The element's type whithin the manifest: Track, Catalog etc. */
70    protected Type elementType = null;
71  
72    /** The element's description */
73    protected String description = null;
74  
75    /** The element's mime type, e. g. 'audio/mp3' */
76    @XmlElement(name = "mimetype")
77    protected MimeType mimeType = null;
78  
79    /** The element's type, e. g. 'track/slide' */
80    @XmlAttribute(name = "type")
81    protected MediaPackageElementFlavor flavor = null;
82  
83    /** The tags */
84    @XmlElementWrapper(name = "tags")
85    @XmlElement(name = "tag")
86    protected SortedSet<String> tags = new TreeSet<String>();
87  
88    /** The element's location */
89    @XmlElement(name = "url")
90    protected URI uri = null;
91  
92    /** Size in bytes */
93    @XmlElement(name = "size")
94    protected Long size = null;
95  
96    /** The element's checksum */
97    @XmlElement(name = "checksum")
98    protected Checksum checksum = null;
99  
100   /** The parent media package */
101   protected MediaPackage mediaPackage = null;
102 
103   /** The optional reference to other elements or series */
104   @XmlAttribute(name = "ref")
105   protected MediaPackageReference reference = null;
106 
107   /** Needed by JAXB */
108   protected AbstractMediaPackageElement() {
109   }
110 
111   /**
112    * Creates a new media package element.
113    *
114    * @param elementType
115    *          the type, e. g. Track, Catalog etc.
116    * @param flavor
117    *          the flavor
118    * @param uri
119    *          the elements location
120    */
121   protected AbstractMediaPackageElement(Type elementType, MediaPackageElementFlavor flavor, URI uri) {
122     this(null, elementType, flavor, uri, null, null, null);
123   }
124 
125   /**
126    * Creates a new media package element.
127    *
128    * @param elementType
129    *          the type, e. g. Track, Catalog etc.
130    * @param flavor
131    *          the flavor
132    * @param uri
133    *          the elements location
134    * @param size
135    *          the element size in bytes
136    * @param checksum
137    *          the element checksum
138    * @param mimeType
139    *          the element mime type
140    */
141   protected AbstractMediaPackageElement(Type elementType, MediaPackageElementFlavor flavor, URI uri, Long size,
142           Checksum checksum, MimeType mimeType) {
143     this(null, elementType, flavor, uri, size, checksum, mimeType);
144   }
145 
146   /**
147    * Creates a new media package element.
148    *
149    * @param id
150    *          the element identifier withing the package
151    * @param elementType
152    *          the type, e. g. Track, Catalog etc.
153    * @param flavor
154    *          the flavor
155    * @param uri
156    *          the elements location
157    * @param size
158    *          the element size in bytes
159    * @param checksum
160    *          the element checksum
161    * @param mimeType
162    *          the element mime type
163    */
164   protected AbstractMediaPackageElement(String id, Type elementType, MediaPackageElementFlavor flavor, URI uri,
165           Long size, Checksum checksum, MimeType mimeType) {
166     if (elementType == null) {
167       throw new IllegalArgumentException("Argument 'elementType' is null");
168     }
169     this.id = id;
170     this.elementType = elementType;
171     this.flavor = flavor;
172     this.mimeType = mimeType;
173     this.uri = uri;
174     this.size = size;
175     this.checksum = checksum;
176     this.tags = new TreeSet<String>();
177   }
178 
179   /**
180    * @see org.opencastproject.mediapackage.MediaPackageElement#setIdentifier(String)
181    */
182   @Override
183   public void setIdentifier(String id) {
184     this.id = id;
185   }
186 
187   @Override
188   public String generateIdentifier() {
189     id = UUID.randomUUID().toString();
190     return id;
191   }
192 
193   /**
194    * @see org.opencastproject.mediapackage.MediaPackageElement#getIdentifier()
195    */
196   @Override
197   public String getIdentifier() {
198     return id;
199   }
200 
201   /**
202    * @see org.opencastproject.mediapackage.MediaPackageElement#setTags(java.lang.String[])
203    */
204   @Override
205   public void setTags(String[] tags) {
206     this.tags = new TreeSet<String>(Arrays.asList(tags));
207   }
208 
209   /**
210    * @see org.opencastproject.mediapackage.MediaPackageElement#addTag(java.lang.String)
211    */
212   @Override
213   public void addTag(String tag) {
214     if (tag == null) {
215       throw new IllegalArgumentException("Tag must not be null");
216     }
217     tags.add(tag);
218   }
219 
220   /**
221    * @see org.opencastproject.mediapackage.MediaPackageElement#removeTag(java.lang.String)
222    */
223   @Override
224   public void removeTag(String tag) {
225     if (tag == null) {
226       return;
227     }
228     tags.remove(tag);
229   }
230 
231   /**
232    * @see org.opencastproject.mediapackage.MediaPackageElement#containsTag(java.lang.String)
233    */
234   @Override
235   public boolean containsTag(String tag) {
236     if (tag == null || tags == null) {
237       return false;
238     }
239     return tags.contains(tag);
240   }
241 
242   /**
243    * @see org.opencastproject.mediapackage.MediaPackageElement#containsTag(java.util.Collection)
244    */
245   @Override
246   public boolean containsTag(Collection<String> tags) {
247     if (tags == null || tags.size() == 0) {
248       return true;
249     }
250     for (String tag : tags) {
251       if (containsTag(tag)) {
252         return true;
253       }
254     }
255     return false;
256   }
257 
258   /**
259    * @see org.opencastproject.mediapackage.MediaPackageElement#getTags()
260    */
261   @Override
262   public String[] getTags() {
263     return tags.toArray(new String[tags.size()]);
264   }
265 
266   /**
267    * @see org.opencastproject.mediapackage.MediaPackageElement#clearTags()
268    */
269   @Override
270   public void clearTags() {
271     if (tags != null) {
272       tags.clear();
273     }
274   }
275 
276   /**
277    * @see org.opencastproject.mediapackage.MediaPackageElement#getMediaPackage()
278    */
279   @Override
280   public MediaPackage getMediaPackage() {
281     return mediaPackage;
282   }
283 
284   /**
285    * @see org.opencastproject.mediapackage.MediaPackageElement#getElementType()
286    */
287   @Override
288   public Type getElementType() {
289     return elementType;
290   }
291 
292   /**
293    * @see org.opencastproject.mediapackage.MediaPackageElement#getElementDescription()
294    */
295   @Override
296   public String getElementDescription() {
297     return (description != null) ? description : uri.toString();
298   }
299 
300   /**
301    * @see org.opencastproject.mediapackage.MediaPackageElement#setElementDescription(String)
302    */
303   @Override
304   public void setElementDescription(String name) {
305     this.description = name;
306   }
307 
308   /**
309    * @see org.opencastproject.mediapackage.MediaPackageElement#getReference()
310    */
311   @Override
312   public MediaPackageReference getReference() {
313     return reference;
314   }
315 
316   /**
317    * @see org.opencastproject.mediapackage.MediaPackageElement#setReference(MediaPackageReference)
318    */
319   @Override
320   public void setReference(MediaPackageReference reference) {
321     this.reference = reference;
322   }
323 
324   /**
325    * @see org.opencastproject.mediapackage.MediaPackageElement#getURI()
326    */
327   @Override
328   public URI getURI() {
329     return uri;
330   }
331 
332   /**
333    * @see org.opencastproject.mediapackage.MediaPackageElement#setURI(java.net.URI)
334    */
335   @Override
336   public void setURI(URI uri) {
337     this.uri = uri;
338   }
339 
340   /**
341    * @see org.opencastproject.mediapackage.MediaPackageElement#getChecksum()
342    */
343   @Override
344   public Checksum getChecksum() {
345     return checksum;
346   }
347 
348   /**
349    * @see org.opencastproject.mediapackage.MediaPackageElement#setChecksum(org.opencastproject.util.Checksum)
350    */
351   @Override
352   public void setChecksum(Checksum checksum) {
353     this.checksum = checksum;
354   }
355 
356   /**
357    * @see org.opencastproject.mediapackage.MediaPackageElement#getMimeType()
358    */
359   @Override
360   public MimeType getMimeType() {
361     return mimeType;
362   }
363 
364   /**
365    * @see org.opencastproject.mediapackage.MediaPackageElement#setMimeType(org.opencastproject.util.MimeType)
366    */
367   @Override
368   public void setMimeType(MimeType mimeType) {
369     this.mimeType = mimeType;
370   }
371 
372   /**
373    * @see org.opencastproject.mediapackage.MediaPackageElement#setFlavor(MediaPackageElementFlavor)
374    */
375   @Override
376   public void setFlavor(MediaPackageElementFlavor flavor) {
377     this.flavor = flavor;
378   }
379 
380   /**
381    * @see org.opencastproject.mediapackage.MediaPackageElement#getFlavor()
382    */
383   @Override
384   public MediaPackageElementFlavor getFlavor() {
385     return flavor;
386   }
387 
388   /**
389    * @see org.opencastproject.mediapackage.MediaPackageElement#getSize()
390    */
391   @Override
392   public long getSize() {
393     return size != null ? size : -1;
394   }
395 
396   /**
397    * @see org.opencastproject.mediapackage.MediaPackageElement#setSize(long)
398    */
399   @Override
400   public void setSize(long size) {
401     this.size = size;
402   }
403 
404   /**
405    * Sets the parent media package.
406    * <p>
407    * <b>Note</b> This method is only used by the media package and should not be called from elsewhere.
408    *
409    * @param mediaPackage
410    *          the parent media package
411    */
412   void setMediaPackage(MediaPackage mediaPackage) {
413     this.mediaPackage = mediaPackage;
414   }
415 
416   /**
417    * @see org.opencastproject.mediapackage.MediaPackageElement#referTo(
418  *        org.opencastproject.mediapackage.MediaPackageElement)
419    */
420   @Override
421   public void referTo(MediaPackageElement element) {
422     referTo(new MediaPackageReferenceImpl(element));
423   }
424 
425   /**
426    * @see org.opencastproject.mediapackage.MediaPackageElement#referTo(
427    *      org.opencastproject.mediapackage.MediaPackageReference)
428    */
429   @Override
430   public void referTo(MediaPackageReference reference) {
431     // TODO: Check reference consistency
432     this.reference = reference;
433   }
434 
435   /**
436    * @see org.opencastproject.mediapackage.MediaPackageElement#clearReference()
437    */
438   @Override
439   public void clearReference() {
440     this.reference = null;
441   }
442 
443   /**
444    * @see org.opencastproject.mediapackage.MediaPackageElement#verify()
445    */
446   @Override
447   public void verify() throws MediaPackageException {
448     // TODO: Check availability at url
449     // TODO: Download (?) and check checksum
450     // Checksum c = calculateChecksum();
451     // if (checksum != null && !checksum.equals(c)) {
452     // throw new MediaPackageException("Checksum mismatch for " + this);
453     // }
454     // checksum = c;
455   }
456 
457   /**
458    * @see java.lang.Comparable#compareTo(java.lang.Object)
459    */
460   @Override
461   public int compareTo(MediaPackageElement o) {
462     return uri.toString().compareTo(o.getURI().toString());
463   }
464 
465   /**
466    * @see java.lang.Object#equals(java.lang.Object)
467    */
468   @Override
469   public boolean equals(Object obj) {
470     if (!(obj instanceof MediaPackageElement)) {
471       return false;
472     }
473     MediaPackageElement e = (MediaPackageElement) obj;
474     if (mediaPackage != null && e.getMediaPackage() != null && !mediaPackage.equals(e.getMediaPackage())) {
475       return false;
476     }
477     if (id != null && !id.equals(e.getIdentifier())) {
478       return false;
479     }
480     if (uri != null && !uri.equals(e.getURI())) {
481       return false;
482     }
483     return true;
484   }
485 
486   /**
487    * @see java.lang.Object#hashCode()
488    */
489   @Override
490   public int hashCode() {
491     final int prime = 31;
492     int result = 1;
493     result = prime * result + ((id == null) ? 0 : id.hashCode());
494     result = prime * result + ((mediaPackage == null) ? 0 : mediaPackage.hashCode());
495     result = prime * result + ((uri == null) ? 0 : uri.hashCode());
496     return result;
497   }
498 
499   /**
500    * @see org.opencastproject.mediapackage.ManifestContributor#toManifest(org.w3c.dom.Document,
501    *      org.opencastproject.mediapackage.MediaPackageSerializer)
502    */
503   @Override
504   public Node toManifest(Document document, MediaPackageSerializer serializer) throws MediaPackageException {
505     Element node = document.createElement(elementType.toString().toLowerCase());
506     if (id != null) {
507       node.setAttribute("id", id);
508     }
509 
510     // Flavor
511     if (flavor != null) {
512       node.setAttribute("type", flavor.toString());
513     }
514 
515     // Reference
516     if (reference != null) {
517       if (mediaPackage == null || !reference.matches(new MediaPackageReferenceImpl(mediaPackage))) {
518         node.setAttribute("ref", reference.toString());
519       }
520     }
521 
522     // Description
523     if (description != null) {
524       Element descriptionNode = document.createElement("description");
525       descriptionNode.appendChild(document.createTextNode(description));
526       node.appendChild(descriptionNode);
527     }
528 
529     // Tags
530     if (tags.size() > 0) {
531       Element tagsNode = document.createElement("tags");
532       node.appendChild(tagsNode);
533       for (String tag : tags) {
534         Element tagNode = document.createElement("tag");
535         tagsNode.appendChild(tagNode);
536         tagNode.appendChild(document.createTextNode(tag));
537       }
538     }
539 
540     // Url
541     Element urlNode = document.createElement("url");
542     String urlValue;
543     try {
544       urlValue = (serializer != null) ? serializer.encodeURI(uri).toString() : uri.toString();
545     } catch (URISyntaxException e) {
546       throw new MediaPackageException(e);
547     }
548     urlNode.appendChild(document.createTextNode(urlValue));
549     node.appendChild(urlNode);
550 
551     // MimeType
552     if (mimeType != null) {
553       Element mimeNode = document.createElement("mimetype");
554       mimeNode.appendChild(document.createTextNode(mimeType.toString()));
555       node.appendChild(mimeNode);
556     }
557 
558     // Size
559     if (size != null && size != -1) {
560       Element sizeNode = document.createElement("size");
561       sizeNode.appendChild(document.createTextNode(Long.toString(size)));
562       node.appendChild(sizeNode);
563     }
564 
565     // Checksum
566     if (checksum != null) {
567       Element checksumNode = document.createElement("checksum");
568       checksumNode.setAttribute("type", checksum.getType().getName());
569       checksumNode.appendChild(document.createTextNode(checksum.getValue()));
570       node.appendChild(checksumNode);
571     }
572 
573     return node;
574   }
575 
576   /**
577    * @see java.lang.Object#toString()
578    */
579   @Override
580   public String toString() {
581     String s = (description != null) ? description : uri.toString();
582     return s.toLowerCase();
583   }
584 
585   /**
586    * Attention: The media package reference is not being cloned so that calling <code>getMediaPackage()</code> on the
587    * clone yields null.
588    */
589   @Override
590   public Object clone() {
591     ByteArrayOutputStream out = new ByteArrayOutputStream();
592     ByteArrayInputStream in = null;
593     try {
594       Marshaller marshaller = MediaPackageImpl.context.createMarshaller();
595       marshaller.marshal(this, out);
596       Unmarshaller unmarshaller = MediaPackageImpl.context.createUnmarshaller();
597       in = new ByteArrayInputStream(out.toByteArray());
598       // CHECKSTYLE:OFF
599       // in was already parsed and is therefore save
600       return unmarshaller.unmarshal(in);
601       // CHECKSTYLE:ON
602     } catch (JAXBException e) {
603       throw new RuntimeException(e.getLinkedException() != null ? e.getLinkedException() : e);
604     } finally {
605       IoSupport.closeQuietly(in);
606     }
607   }
608 
609 }