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     this.id = id;
169     this.elementType = elementType;
170     this.flavor = flavor;
171     this.mimeType = mimeType;
172     this.uri = uri;
173     this.size = size;
174     this.checksum = checksum;
175     this.tags = new TreeSet<String>();
176   }
177 
178   /**
179    * @see org.opencastproject.mediapackage.MediaPackageElement#setIdentifier(String)
180    */
181   @Override
182   public void setIdentifier(String id) {
183     this.id = id;
184   }
185 
186   @Override
187   public String generateIdentifier() {
188     id = UUID.randomUUID().toString();
189     return id;
190   }
191 
192   /**
193    * @see org.opencastproject.mediapackage.MediaPackageElement#getIdentifier()
194    */
195   @Override
196   public String getIdentifier() {
197     return id;
198   }
199 
200   /**
201    * @see org.opencastproject.mediapackage.MediaPackageElement#setTags(java.lang.String[])
202    */
203   @Override
204   public void setTags(String[] tags) {
205     this.tags = new TreeSet<String>(Arrays.asList(tags));
206   }
207 
208   /**
209    * @see org.opencastproject.mediapackage.MediaPackageElement#addTag(java.lang.String)
210    */
211   @Override
212   public void addTag(String tag) {
213     if (tag == null)
214       throw new IllegalArgumentException("Tag must not be null");
215     tags.add(tag);
216   }
217 
218   /**
219    * @see org.opencastproject.mediapackage.MediaPackageElement#removeTag(java.lang.String)
220    */
221   @Override
222   public void removeTag(String tag) {
223     if (tag == null)
224       return;
225     tags.remove(tag);
226   }
227 
228   /**
229    * @see org.opencastproject.mediapackage.MediaPackageElement#containsTag(java.lang.String)
230    */
231   @Override
232   public boolean containsTag(String tag) {
233     if (tag == null || tags == null)
234       return false;
235     return tags.contains(tag);
236   }
237 
238   /**
239    * @see org.opencastproject.mediapackage.MediaPackageElement#containsTag(java.util.Collection)
240    */
241   @Override
242   public boolean containsTag(Collection<String> tags) {
243     if (tags == null || tags.size() == 0)
244       return true;
245     for (String tag : tags) {
246       if (containsTag(tag))
247         return true;
248     }
249     return false;
250   }
251 
252   /**
253    * @see org.opencastproject.mediapackage.MediaPackageElement#getTags()
254    */
255   @Override
256   public String[] getTags() {
257     return tags.toArray(new String[tags.size()]);
258   }
259 
260   /**
261    * @see org.opencastproject.mediapackage.MediaPackageElement#clearTags()
262    */
263   @Override
264   public void clearTags() {
265     if (tags != null)
266       tags.clear();
267   }
268 
269   /**
270    * @see org.opencastproject.mediapackage.MediaPackageElement#getMediaPackage()
271    */
272   @Override
273   public MediaPackage getMediaPackage() {
274     return mediaPackage;
275   }
276 
277   /**
278    * @see org.opencastproject.mediapackage.MediaPackageElement#getElementType()
279    */
280   @Override
281   public Type getElementType() {
282     return elementType;
283   }
284 
285   /**
286    * @see org.opencastproject.mediapackage.MediaPackageElement#getElementDescription()
287    */
288   @Override
289   public String getElementDescription() {
290     return (description != null) ? description : uri.toString();
291   }
292 
293   /**
294    * @see org.opencastproject.mediapackage.MediaPackageElement#setElementDescription(String)
295    */
296   @Override
297   public void setElementDescription(String name) {
298     this.description = name;
299   }
300 
301   /**
302    * @see org.opencastproject.mediapackage.MediaPackageElement#getReference()
303    */
304   @Override
305   public MediaPackageReference getReference() {
306     return reference;
307   }
308 
309   /**
310    * @see org.opencastproject.mediapackage.MediaPackageElement#setReference(MediaPackageReference)
311    */
312   @Override
313   public void setReference(MediaPackageReference reference) {
314     this.reference = reference;
315   }
316 
317   /**
318    * @see org.opencastproject.mediapackage.MediaPackageElement#getURI()
319    */
320   @Override
321   public URI getURI() {
322     return uri;
323   }
324 
325   /**
326    * @see org.opencastproject.mediapackage.MediaPackageElement#setURI(java.net.URI)
327    */
328   @Override
329   public void setURI(URI uri) {
330     this.uri = uri;
331   }
332 
333   /**
334    * @see org.opencastproject.mediapackage.MediaPackageElement#getChecksum()
335    */
336   @Override
337   public Checksum getChecksum() {
338     return checksum;
339   }
340 
341   /**
342    * @see org.opencastproject.mediapackage.MediaPackageElement#setChecksum(org.opencastproject.util.Checksum)
343    */
344   @Override
345   public void setChecksum(Checksum checksum) {
346     this.checksum = checksum;
347   }
348 
349   /**
350    * @see org.opencastproject.mediapackage.MediaPackageElement#getMimeType()
351    */
352   @Override
353   public MimeType getMimeType() {
354     return mimeType;
355   }
356 
357   /**
358    * @see org.opencastproject.mediapackage.MediaPackageElement#setMimeType(org.opencastproject.util.MimeType)
359    */
360   @Override
361   public void setMimeType(MimeType mimeType) {
362     this.mimeType = mimeType;
363   }
364 
365   /**
366    * @see org.opencastproject.mediapackage.MediaPackageElement#setFlavor(MediaPackageElementFlavor)
367    */
368   @Override
369   public void setFlavor(MediaPackageElementFlavor flavor) {
370     this.flavor = flavor;
371   }
372 
373   /**
374    * @see org.opencastproject.mediapackage.MediaPackageElement#getFlavor()
375    */
376   @Override
377   public MediaPackageElementFlavor getFlavor() {
378     return flavor;
379   }
380 
381   /**
382    * @see org.opencastproject.mediapackage.MediaPackageElement#getSize()
383    */
384   @Override
385   public long getSize() {
386     return size != null ? size : -1;
387   }
388 
389   /**
390    * @see org.opencastproject.mediapackage.MediaPackageElement#setSize(long)
391    */
392   @Override
393   public void setSize(long size) {
394     this.size = size;
395   }
396 
397   /**
398    * Sets the parent media package.
399    * <p>
400    * <b>Note</b> This method is only used by the media package and should not be called from elsewhere.
401    *
402    * @param mediaPackage
403    *          the parent media package
404    */
405   void setMediaPackage(MediaPackage mediaPackage) {
406     this.mediaPackage = mediaPackage;
407   }
408 
409   /**
410    * @see org.opencastproject.mediapackage.MediaPackageElement#referTo(org.opencastproject.mediapackage.MediaPackageElement)
411    */
412   @Override
413   public void referTo(MediaPackageElement element) {
414     referTo(new MediaPackageReferenceImpl(element));
415   }
416 
417   /**
418    * @see org.opencastproject.mediapackage.MediaPackageElement#referTo(org.opencastproject.mediapackage.MediaPackageReference)
419    */
420   @Override
421   public void referTo(MediaPackageReference reference) {
422     // TODO: Check reference consistency
423     this.reference = reference;
424   }
425 
426   /**
427    * @see org.opencastproject.mediapackage.MediaPackageElement#clearReference()
428    */
429   @Override
430   public void clearReference() {
431     this.reference = null;
432   }
433 
434   /**
435    * @see org.opencastproject.mediapackage.MediaPackageElement#verify()
436    */
437   @Override
438   public void verify() throws MediaPackageException {
439     // TODO: Check availability at url
440     // TODO: Download (?) and check checksum
441     // Checksum c = calculateChecksum();
442     // if (checksum != null && !checksum.equals(c)) {
443     // throw new MediaPackageException("Checksum mismatch for " + this);
444     // }
445     // checksum = c;
446   }
447 
448   /**
449    * @see java.lang.Comparable#compareTo(java.lang.Object)
450    */
451   @Override
452   public int compareTo(MediaPackageElement o) {
453     return uri.toString().compareTo(o.getURI().toString());
454   }
455 
456   /**
457    * @see java.lang.Object#equals(java.lang.Object)
458    */
459   @Override
460   public boolean equals(Object obj) {
461     if (!(obj instanceof MediaPackageElement))
462       return false;
463     MediaPackageElement e = (MediaPackageElement) obj;
464     if (mediaPackage != null && e.getMediaPackage() != null && !mediaPackage.equals(e.getMediaPackage()))
465       return false;
466     if (id != null && !id.equals(e.getIdentifier()))
467       return false;
468     if (uri != null && !uri.equals(e.getURI()))
469       return false;
470     return true;
471   }
472 
473   /**
474    * @see java.lang.Object#hashCode()
475    */
476   @Override
477   public int hashCode() {
478     final int prime = 31;
479     int result = 1;
480     result = prime * result + ((id == null) ? 0 : id.hashCode());
481     result = prime * result + ((mediaPackage == null) ? 0 : mediaPackage.hashCode());
482     result = prime * result + ((uri == null) ? 0 : uri.hashCode());
483     return result;
484   }
485 
486   /**
487    * @see org.opencastproject.mediapackage.ManifestContributor#toManifest(org.w3c.dom.Document,
488    *      org.opencastproject.mediapackage.MediaPackageSerializer)
489    */
490   @Override
491   public Node toManifest(Document document, MediaPackageSerializer serializer) throws MediaPackageException {
492     Element node = document.createElement(elementType.toString().toLowerCase());
493     if (id != null)
494       node.setAttribute("id", id);
495 
496     // Flavor
497     if (flavor != null)
498       node.setAttribute("type", flavor.toString());
499 
500     // Reference
501     if (reference != null)
502       if (mediaPackage == null || !reference.matches(new MediaPackageReferenceImpl(mediaPackage)))
503         node.setAttribute("ref", reference.toString());
504 
505     // Description
506     if (description != null) {
507       Element descriptionNode = document.createElement("description");
508       descriptionNode.appendChild(document.createTextNode(description));
509       node.appendChild(descriptionNode);
510     }
511 
512     // Tags
513     if (tags.size() > 0) {
514       Element tagsNode = document.createElement("tags");
515       node.appendChild(tagsNode);
516       for (String tag : tags) {
517         Element tagNode = document.createElement("tag");
518         tagsNode.appendChild(tagNode);
519         tagNode.appendChild(document.createTextNode(tag));
520       }
521     }
522 
523     // Url
524     Element urlNode = document.createElement("url");
525     String urlValue;
526     try {
527       urlValue = (serializer != null) ? serializer.encodeURI(uri).toString() : uri.toString();
528     } catch (URISyntaxException e) {
529       throw new MediaPackageException(e);
530     }
531     urlNode.appendChild(document.createTextNode(urlValue));
532     node.appendChild(urlNode);
533 
534     // MimeType
535     if (mimeType != null) {
536       Element mimeNode = document.createElement("mimetype");
537       mimeNode.appendChild(document.createTextNode(mimeType.toString()));
538       node.appendChild(mimeNode);
539     }
540 
541     // Size
542     if (size != null && size != -1) {
543       Element sizeNode = document.createElement("size");
544       sizeNode.appendChild(document.createTextNode(Long.toString(size)));
545       node.appendChild(sizeNode);
546     }
547 
548     // Checksum
549     if (checksum != null) {
550       Element checksumNode = document.createElement("checksum");
551       checksumNode.setAttribute("type", checksum.getType().getName());
552       checksumNode.appendChild(document.createTextNode(checksum.getValue()));
553       node.appendChild(checksumNode);
554     }
555 
556     return node;
557   }
558 
559   /**
560    * @see java.lang.Object#toString()
561    */
562   @Override
563   public String toString() {
564     String s = (description != null) ? description : uri.toString();
565     return s.toLowerCase();
566   }
567 
568   /**
569    * Attention: The media package reference is not being cloned so that calling <code>getMediaPackage()</code> on the
570    * clone yields null.
571    */
572   @Override
573   public Object clone() {
574     ByteArrayOutputStream out = new ByteArrayOutputStream();
575     ByteArrayInputStream in = null;
576     try {
577       Marshaller marshaller = MediaPackageImpl.context.createMarshaller();
578       marshaller.marshal(this, out);
579       Unmarshaller unmarshaller = MediaPackageImpl.context.createUnmarshaller();
580       in = new ByteArrayInputStream(out.toByteArray());
581       // CHECKSTYLE:OFF
582       // in was already parsed and is therefore save
583       return unmarshaller.unmarshal(in);
584       // CHECKSTYLE:ON
585     } catch (JAXBException e) {
586       throw new RuntimeException(e.getLinkedException() != null ? e.getLinkedException() : e);
587     } finally {
588       IoSupport.closeQuietly(in);
589     }
590   }
591 
592 }