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.elasticsearch.index.objects.event;
23  
24  import org.opencastproject.elasticsearch.index.objects.IndexObject;
25  import org.opencastproject.mediapackage.Publication;
26  import org.opencastproject.scheduler.api.RecordingState;
27  import org.opencastproject.util.IoSupport;
28  import org.opencastproject.util.XmlSafeParser;
29  import org.opencastproject.util.jaxb.ExtendedMetadataAdapter;
30  import org.opencastproject.workflow.api.WorkflowInstance.WorkflowState;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.codehaus.jettison.json.JSONException;
34  import org.codehaus.jettison.json.JSONObject;
35  import org.codehaus.jettison.mapped.Configuration;
36  import org.codehaus.jettison.mapped.MappedNamespaceConvention;
37  import org.codehaus.jettison.mapped.MappedXMLStreamReader;
38  import org.codehaus.jettison.mapped.MappedXMLStreamWriter;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  import org.xml.sax.SAXException;
42  
43  import java.io.BufferedReader;
44  import java.io.IOException;
45  import java.io.InputStream;
46  import java.io.InputStreamReader;
47  import java.io.StringWriter;
48  import java.util.ArrayList;
49  import java.util.HashMap;
50  import java.util.List;
51  import java.util.Map;
52  
53  import javax.xml.bind.JAXBContext;
54  import javax.xml.bind.JAXBException;
55  import javax.xml.bind.Marshaller;
56  import javax.xml.bind.Unmarshaller;
57  import javax.xml.bind.annotation.XmlAccessType;
58  import javax.xml.bind.annotation.XmlAccessorType;
59  import javax.xml.bind.annotation.XmlElement;
60  import javax.xml.bind.annotation.XmlElementWrapper;
61  import javax.xml.bind.annotation.XmlRootElement;
62  import javax.xml.bind.annotation.XmlType;
63  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
64  import javax.xml.stream.XMLStreamException;
65  import javax.xml.stream.XMLStreamReader;
66  import javax.xml.stream.XMLStreamWriter;
67  
68  /**
69   * Object wrapper for a recording event.
70   */
71  @XmlType(
72      name = "event",
73      namespace = IndexObject.INDEX_XML_NAMESPACE,
74      propOrder = {
75          "identifier", "organization", "title", "description", "subject", "location", "presenters",
76          "contributors", "seriesId", "seriesName", "language", "source", "created", "creator",
77          "publisher", "license", "rights", "extendedMetadata", "accessPolicy", "managedAcl", "workflowState",
78          "workflowId", "workflowDefinitionId", "recordingStartTime", "recordingEndTime", "duration",
79          "hasComments", "hasOpenComments", "comments", "hasPreview", "needsCutting", "publications",
80          "archiveVersion", "recordingStatus", "eventStatus", "agentId", "agentConfigurations",
81          "technicalStartTime", "technicalEndTime", "technicalPresenters"
82      }
83  )
84  @XmlRootElement(name = "event", namespace = IndexObject.INDEX_XML_NAMESPACE)
85  @XmlAccessorType(XmlAccessType.NONE)
86  public class Event implements IndexObject {
87  
88    /** The logger */
89    private static final Logger logger = LoggerFactory.getLogger(Event.class);
90  
91    /** The document type */
92    public static final String DOCUMENT_TYPE = "event";
93  
94    /** The name of the surrounding XML tag to wrap a result of multiple events */
95    public static final String XML_SURROUNDING_TAG = "events";
96  
97    /** The mapping of recording and workflow states */
98    private static final Map<String, String> workflowStatusMapping = new HashMap<>();
99    private static final Map<String, String> recordingStatusMapping = new HashMap<>();
100 
101   static {
102     recordingStatusMapping.put(RecordingState.CAPTURING, "EVENTS.EVENTS.STATUS.RECORDING");
103     recordingStatusMapping.put(RecordingState.CAPTURE_FINISHED, "EVENTS.EVENTS.STATUS.RECORDING");
104     recordingStatusMapping.put(RecordingState.MANIFEST, "EVENTS.EVENTS.STATUS.INGESTING");
105     recordingStatusMapping.put(RecordingState.MANIFEST_FINISHED, "EVENTS.EVENTS.STATUS.INGESTING");
106     recordingStatusMapping.put(RecordingState.COMPRESSING, "EVENTS.EVENTS.STATUS.INGESTING");
107     recordingStatusMapping.put(RecordingState.UPLOADING, "EVENTS.EVENTS.STATUS.INGESTING");
108     recordingStatusMapping.put(RecordingState.UPLOAD_FINISHED, "EVENTS.EVENTS.STATUS.INGESTING");
109     recordingStatusMapping.put(RecordingState.CAPTURE_ERROR, "EVENTS.EVENTS.STATUS.RECORDING_FAILURE");
110     recordingStatusMapping.put(RecordingState.MANIFEST_ERROR, "EVENTS.EVENTS.STATUS.RECORDING_FAILURE");
111     recordingStatusMapping.put(RecordingState.COMPRESSING_ERROR, "EVENTS.EVENTS.STATUS.RECORDING_FAILURE");
112     recordingStatusMapping.put(RecordingState.UPLOAD_ERROR, "EVENTS.EVENTS.STATUS.RECORDING_FAILURE");
113     workflowStatusMapping.put(WorkflowState.INSTANTIATED.toString(), "EVENTS.EVENTS.STATUS.PENDING");
114     workflowStatusMapping.put(WorkflowState.RUNNING.toString(), "EVENTS.EVENTS.STATUS.PROCESSING");
115     workflowStatusMapping.put(WorkflowState.FAILING.toString(), "EVENTS.EVENTS.STATUS.PROCESSING");
116     workflowStatusMapping.put(WorkflowState.PAUSED.toString(), "EVENTS.EVENTS.STATUS.PAUSED");
117     workflowStatusMapping.put(WorkflowState.SUCCEEDED.toString(), "EVENTS.EVENTS.STATUS.PROCESSED");
118     workflowStatusMapping.put(WorkflowState.FAILED.toString(), "EVENTS.EVENTS.STATUS.PROCESSING_FAILURE");
119     workflowStatusMapping.put(WorkflowState.STOPPED.toString(), "EVENTS.EVENTS.STATUS.PROCESSING_CANCELLED");
120   }
121 
122   /** The identifier */
123   @XmlElement(name = "identifier")
124   private String identifier = null;
125 
126   /** The organization identifier */
127   @XmlElement(name = "organization")
128   private String organization = null;
129 
130   /** The title */
131   @XmlElement(name = "title")
132   private String title = null;
133 
134   /** The description */
135   @XmlElement(name = "description")
136   private String description = null;
137 
138   /** The subject */
139   @XmlElement(name = "subject")
140   private String subject = null;
141 
142   /** The location */
143   @XmlElement(name = "location")
144   private String location = null;
145 
146   @XmlElementWrapper(name = "presenters")
147   @XmlElement(name = "presenter")
148   private List<String> presenters = null;
149 
150   @XmlElementWrapper(name = "contributors")
151   @XmlElement(name = "contributor")
152   private List<String> contributors = null;
153 
154   /** The series identifier */
155   @XmlElement(name = "series_id")
156   private String seriesId = null;
157 
158   /** The series name */
159   @XmlElement(name = "series_name")
160   private String seriesName = null;
161 
162   /** The language for the event */
163   @XmlElement(name = "language")
164   private String language = null;
165 
166   /** The source for the event */
167   @XmlElement(name = "source")
168   private String source = null;
169 
170   /** The creation date of the event */
171   @XmlElement(name = "created")
172   private String created = null;
173 
174   /** The creator of the event */
175   @XmlElement(name = "creator")
176   private String creator = null;
177 
178   /** The publisher of the event */
179   @XmlElement(name = "publisher")
180   private String publisher = null;
181 
182   /** The license of the event */
183   @XmlElement(name = "license")
184   private String license = null;
185 
186   /** The rights of the event */
187   @XmlElement(name = "rights")
188   private String rights = null;
189 
190   @XmlElement(name = "extendedMetadata")
191   @XmlJavaTypeAdapter(ExtendedMetadataAdapter.class)
192   private Map<String, Map<String, List<String>>> extendedMetadata = new HashMap();
193 
194   /** The access policy of the event */
195   @XmlElement(name = "access_policy")
196   private String accessPolicy = null;
197 
198   /** The name of the managed ACL used by the series (if set) */
199   @XmlElement(name = "managed_acl")
200   private String managedAcl = null;
201 
202   /** The current state of the workflow related to this event */
203   @XmlElement(name = "workflow_state")
204   private String workflowState = null;
205 
206   /** The workflow id related to this event */
207   @XmlElement(name = "workflow_id")
208   private Long workflowId = null;
209 
210   /** The workflow definition id related to this event */
211   @XmlElement(name = "workflow_definition_id")
212   private String workflowDefinitionId = null;
213 
214   /** The recording start date from this event */
215   @XmlElement(name = "recording_start_time")
216   private String recordingStartTime = null;
217 
218   /** The recording end date from this event */
219   @XmlElement(name = "recording_end_time")
220   private String recordingEndTime = null;
221 
222   /** The recording duration from this event */
223   @XmlElement(name = "duration")
224   private Long duration = null;
225 
226   /** The status of the event */
227   @XmlElement(name = "event_status")
228   private String eventStatus = null;
229 
230   /** Whether the event has comments */
231   @XmlElement(name = "has_comments")
232   private Boolean hasComments = false;
233 
234   /** Whether the event has open comments */
235   @XmlElement(name = "has_open_comments")
236   private Boolean hasOpenComments = false;
237 
238   /** Comments on the event */
239   @XmlElement(name = "comments")
240   private List<Comment> comments = new ArrayList<>();
241 
242   /** Whether the event has preview files */
243   @XmlElement(name = "has_preview")
244   private Boolean hasPreview = false;
245 
246   /** Whether the event has open needs cutting comment */
247   @XmlElement(name = "needs_cutting")
248   private Boolean needsCutting = false;
249 
250   /** The list of publications from this event */
251   @XmlElementWrapper(name = "publications")
252   @XmlElement(name = "publication")
253   private List<Publication> publications = new ArrayList<>();
254 
255   /** The recording status of the event */
256   @XmlElement(name = "recording_status")
257   private String recordingStatus = null;
258 
259   /** The archive version of the event */
260   @XmlElement(name = "archive_version")
261   private Long archiveVersion = null;
262 
263   /** The id of the capture agent */
264   @XmlElement(name = "agent_id")
265   private String agentId = null;
266 
267   /** The configuration of the capture agent */
268   @XmlElementWrapper(name = "agent_configuration")
269   private Map<String, String> agentConfigurations = new HashMap<String, String>();
270 
271   /** The technical end time of the recording */
272   @XmlElement(name = "technical_end_time")
273   private String technicalEndTime = null;
274 
275   /** The technical start time of the recording */
276   @XmlElement(name = "technical_start_time")
277   private String technicalStartTime = null;
278 
279   @XmlElementWrapper(name = "technical_presenters")
280   @XmlElement(name = "technical_presenter")
281   private List<String> technicalPresenters = null;
282 
283   /** Context for serializing and deserializing */
284   private static JAXBContext context = null;
285 
286   /**
287    * Required default no arg constructor for JAXB.
288    */
289   public Event() {
290 
291   }
292 
293   /**
294    * The recording identifier.
295    *
296    * @param identifier
297    *          the object identifier
298    * @param organization
299    *          the organization
300    */
301   public Event(String identifier, String organization) {
302     this.identifier = identifier;
303     this.organization = organization;
304     updateEventStatus();
305   }
306 
307   /**
308    * Returns the recording event identifier.
309    *
310    * @return the identifier
311    */
312   public String getIdentifier() {
313     return identifier;
314   }
315 
316   /**
317    * Returns the organization of the recording.
318    *
319    * @return the organization
320    */
321   public String getOrganization() {
322     return organization;
323   }
324 
325   /**
326    * Sets the recording title.
327    *
328    * @param title
329    *          the title
330    */
331   public void setTitle(String title) {
332     this.title = title;
333   }
334 
335   /**
336    * Returns the recording title.
337    *
338    * @return the title
339    */
340   public String getTitle() {
341     return title;
342   }
343 
344   /**
345    * Sets the recording description.
346    *
347    * @param description
348    *          the description
349    */
350   public void setDescription(String description) {
351     this.description = description;
352   }
353 
354   /**
355    * Returns the recording description.
356    *
357    * @return the description
358    */
359   public String getDescription() {
360     return description;
361   }
362 
363   /**
364    * Sets the recording subject
365    *
366    * @param subject
367    *          the subject to set
368    */
369   public void setSubject(String subject) {
370     this.subject = subject;
371   }
372 
373   /**
374    * Returns the recording subject.
375    *
376    * @return the subject
377    */
378   public String getSubject() {
379     return subject;
380   }
381 
382   /**
383    * Sets the recording location.
384    *
385    * @param location
386    *          the location
387    */
388   public void setLocation(String location) {
389     this.location = location;
390   }
391 
392   /**
393    * Returns the recording location.
394    *
395    * @return the location
396    */
397   public String getLocation() {
398     return location;
399   }
400 
401   /**
402    * Sets the series identifier
403    *
404    * @param seriesId
405    *          the series identifier
406    */
407   public void setSeriesId(String seriesId) {
408     this.seriesId = seriesId;
409   }
410 
411   /**
412    * Returns the series identifier
413    *
414    * @return the series identifier
415    */
416   public String getSeriesId() {
417     return seriesId;
418   }
419 
420   /**
421    * Sets the series name
422    *
423    * @param seriesName
424    *          the series name
425    */
426   public void setSeriesName(String seriesName) {
427     this.seriesName = seriesName;
428   }
429 
430   /**
431    * Returns the series name
432    *
433    * @return the series name
434    */
435   public String getSeriesName() {
436     return seriesName;
437   }
438 
439   /**
440    * Sets the language
441    *
442    * @param language
443    *          the language
444    */
445   public void setLanguage(String language) {
446     this.language = language;
447   }
448 
449   /**
450    * Returns the language
451    *
452    * @return the language
453    */
454   public String getLanguage() {
455     return language;
456   }
457 
458   /**
459    * Sets the source of this event
460    *
461    * @param source
462    *          the source
463    */
464   public void setSource(String source) {
465     this.source = source;
466   }
467 
468   /**
469    * Returns the source of this event
470    *
471    * @return the source
472    */
473   public String getSource() {
474     return source;
475   }
476 
477   /**
478    * Sets the creation date
479    *
480    * @param created
481    *          the creation date
482    */
483   public void setCreated(String created) {
484     this.created = created;
485   }
486 
487   /**
488    * Returns the creation date
489    *
490    * @return the creation date
491    */
492   public String getCreated() {
493     return created;
494   }
495 
496   /**
497    * Sets the creator
498    *
499    * @param creator
500    *          the language
501    */
502   public void setCreator(String creator) {
503     this.creator = creator;
504   }
505 
506   /**
507    * Returns the creator
508    *
509    * @return the language
510    */
511   public String getCreator() {
512     return creator;
513   }
514 
515   /**
516    * Sets the publisher
517    *
518    * @param publisher
519    *          the publisher
520    */
521   public void setPublisher(String publisher) {
522     this.publisher = publisher;
523   }
524 
525   /**
526    * Returns the publisher
527    *
528    * @return the publisher
529    */
530   public String getPublisher() {
531     return publisher;
532   }
533 
534   /**
535    * Sets the license
536    *
537    * @param license
538    *          the language
539    */
540   public void setLicense(String license) {
541     this.license = license;
542   }
543 
544   /**
545    * Returns the license
546    *
547    * @return the license
548    */
549   public String getLicense() {
550     return license;
551   }
552 
553   /**
554    * Sets the rights
555    *
556    * @param rights
557    *          the rights
558    */
559   public void setRights(String rights) {
560     this.rights = rights;
561   }
562 
563   /**
564    * Returns the rights
565    *
566    * @return the rights
567    */
568   public String getRights() {
569     return rights;
570   }
571 
572   /**
573    * Sets the access policy
574    *
575    * @param accessPolicy
576    *          the access policy
577    */
578   public void setAccessPolicy(String accessPolicy) {
579     this.accessPolicy = accessPolicy;
580   }
581 
582   /**
583    * Returns the access policy
584    *
585    * @return the access policy
586    */
587   public String getAccessPolicy() {
588     return accessPolicy;
589   }
590 
591   /**
592    * Sets the name of the managed ACL used by the event.
593    *
594    * @param managedAcl
595    *          the managed ACL name
596    */
597   public void setManagedAcl(String managedAcl) {
598     this.managedAcl = managedAcl;
599   }
600 
601   /**
602    * Returns the name of the managed ACL, if the event does not have a custom ACL.
603    *
604    * @return the managed ACL name
605    */
606   public String getManagedAcl() {
607     return managedAcl;
608   }
609 
610   /**
611    * Sets the current workflow state related to this event
612    *
613    * @param workflowState
614    *          the current workflow state
615    */
616   public void setWorkflowState(WorkflowState workflowState) {
617     this.workflowState = workflowState == null ? null : workflowState.toString();
618     updateEventStatus();
619   }
620 
621   /**
622    * Returns the current workflow state related to this event
623    *
624    * @return the workflow state
625    */
626   public String getWorkflowState() {
627     return workflowState;
628   }
629 
630   /**
631    * Sets the current workflow id related to this event
632    *
633    * @param workflowId
634    *          the current workflow id
635    */
636   public void setWorkflowId(Long workflowId) {
637     this.workflowId = workflowId;
638   }
639 
640   /**
641    * Returns the current workflow id related to this event
642    *
643    * @return the workflow id
644    */
645   public Long getWorkflowId() {
646     return workflowId;
647   }
648 
649   /**
650    * Sets the current workflow definition id related to this event
651    *
652    * @param workflowDefinitionId
653    *          the current workflow definition id
654    */
655   public void setWorkflowDefinitionId(String workflowDefinitionId) {
656     this.workflowDefinitionId = workflowDefinitionId;
657   }
658 
659   /**
660    * Returns the current workflow definition id related to this event
661    *
662    * @return the workflow definition id
663    */
664   public String getWorkflowDefinitionId() {
665     return workflowDefinitionId;
666   }
667 
668   /**
669    * Sets the start date
670    *
671    * @param recordingStartTime
672    *          the start date
673    */
674   public void setRecordingStartDate(String recordingStartTime) {
675     this.recordingStartTime = recordingStartTime;
676   }
677 
678   /**
679    * Returns the start date
680    *
681    * @return the start date
682    */
683   public String getRecordingStartDate() {
684     return recordingStartTime;
685   }
686 
687   /**
688    * Sets the recording end date
689    *
690    * @param recordingEndTime
691    *          the end date
692    */
693   public void setRecordingEndDate(String recordingEndTime) {
694     this.recordingEndTime = recordingEndTime;
695   }
696 
697   /**
698    * Returns the recording end date
699    *
700    * @return the end date
701    */
702   public String getRecordingEndDate() {
703     return recordingEndTime;
704   }
705 
706   /**
707    * Sets the recording duration
708    *
709    * @param duration
710    *          the recording duration
711    */
712   public void setDuration(long duration) {
713     this.duration = duration;
714   }
715 
716   /**
717    * Returns the recording duration
718    *
719    * @return the recording duration
720    */
721   public Long getDuration() {
722     return duration;
723   }
724 
725   /**
726    * Sets the list of presenters.
727    *
728    * @param presenters
729    *          the presenters for this event
730    */
731   public void setPresenters(List<String> presenters) {
732     this.presenters = presenters;
733   }
734 
735   /**
736    * Returns the recording presenters.
737    *
738    * @return the presenters
739    */
740   public List<String> getPresenters() {
741     return presenters;
742   }
743 
744   /**
745    * Sets the list of contributors.
746    *
747    * @param contributors
748    *          the contributors for this event
749    */
750   public void setContributors(List<String> contributors) {
751     this.contributors = contributors;
752   }
753 
754   /**
755    * Returns the recording contributors.
756    *
757    * @return the contributors
758    */
759   public List<String> getContributors() {
760     return contributors;
761   }
762 
763   /**
764    * Sets the has comments status from this event
765    *
766    * @param hasComments
767    *          the has comments status from this event
768    */
769   public void setHasComments(boolean hasComments) {
770     this.hasComments = hasComments;
771   }
772 
773   /**
774    * Returns the has comments status from this event
775    *
776    * @return the has comments status from this event
777    */
778   public boolean hasComments() {
779     return hasComments;
780   }
781 
782   /**
783    * Sets the has open comments status from this event
784    *
785    * @param hasOpenComments
786    *          the has open comments status from this event
787    */
788   public void setHasOpenComments(boolean hasOpenComments) {
789     this.hasOpenComments = hasOpenComments;
790   }
791 
792   /**
793    * Returns the has comments status from this event
794    *
795    * @return the has comments status from this event
796    */
797   public boolean hasOpenComments() {
798     return hasOpenComments;
799   }
800 
801   /**
802    * Sets the list of comments.
803    *
804    * @param comments
805    *          the comments for this event
806    */
807   public void setComments(List<Comment> comments) {
808     this.comments = comments;
809   }
810 
811   /**
812    * Returns the event comments.
813    *
814    * @return the comments
815    */
816   public List<Comment> comments() {
817     return comments;
818   }
819 
820   /**
821    * Sets the has preview status from this event
822    *
823    * @param hasPreview
824    *          the has preview status from this event
825    */
826   public void setHasPreview(boolean hasPreview) {
827     this.hasPreview = hasPreview;
828   }
829 
830   /**
831    * Returns the has preview status from this event
832    *
833    * @return the has preview status from this event
834    */
835   public boolean hasPreview() {
836     return hasPreview;
837   }
838 
839   /**
840    * Sets the subtype of the publication element that indicates a preview element.
841    *
842    * @param previewSubtype
843    *          the subtype
844    */
845   public void updatePreview(String previewSubtype) {
846     hasPreview = EventIndexUtils.subflavorMatches(publications, previewSubtype);
847   }
848 
849   /**
850    * Sets the has open needs cutting comment for this event
851    *
852    * @param needsCutting
853    *          this event has the open comments status that it needs cutting
854    */
855   public void setNeedsCutting(boolean needsCutting) {
856     this.needsCutting = needsCutting;
857   }
858 
859   /**
860    * Returns the has comment needs cutting for this event
861    *
862    * @return the event has the comments status that it needs cutting
863    */
864   public boolean needsCutting() {
865     return needsCutting;
866   }
867 
868   /**
869    * Sets the list of publications.
870    *
871    * @param publications
872    *          the publications for this event
873    */
874   public void setPublications(List<Publication> publications) {
875     this.publications = publications;
876   }
877 
878   /**
879    * Returns the event publications.
880    *
881    * @return the publications
882    */
883   public List<Publication> getPublications() {
884     return publications;
885   }
886 
887   /**
888    * Sets the archive version
889    *
890    * @param archiveVersion
891    *          the archive version
892    */
893   public void setArchiveVersion(Long archiveVersion) {
894     this.archiveVersion = archiveVersion;
895   }
896 
897   /**
898    * Returns the archive version
899    *
900    * @return the archive version
901    */
902   public Long getArchiveVersion() {
903     return archiveVersion;
904   }
905 
906   private void updateEventStatus() {
907     if (getWorkflowId() != null && StringUtils.isBlank(getWorkflowState())
908             || getWorkflowId() == null && StringUtils.isNotBlank(getWorkflowState())) {
909       logger.warn("The workflow id {} and workflow state {} are not in sync on event {} organization {}",
910               getWorkflowId(), getWorkflowState(), getIdentifier(), getOrganization());
911     }
912 
913     if (getWorkflowId() != null && StringUtils.isNotBlank(getWorkflowState())) {
914       eventStatus = workflowStatusMapping.get(getWorkflowState());
915     } else if ("EVENTS.EVENTS.STATUS.PROCESSED".equals(eventStatus)
916             && (RecordingState.CAPTURE_FINISHED.equals(getRecordingStatus())
917             || RecordingState.UPLOAD_FINISHED.equals(getRecordingStatus()))) {
918       eventStatus = "EVENTS.EVENTS.STATUS.PROCESSED";
919     } else if ("EVENTS.EVENTS.STATUS.PROCESSING_FAILURE".equals(eventStatus)
920             && RecordingState.CAPTURE_ERROR.equals(getRecordingStatus())
921             || RecordingState.UPLOAD_ERROR.equals(getRecordingStatus())) {
922       eventStatus = recordingStatusMapping.get(getRecordingStatus());
923     } else if (StringUtils.isNotBlank(getRecordingStatus())) {
924       eventStatus = recordingStatusMapping.get(getRecordingStatus());
925     } else if (isScheduledEvent()) {
926       eventStatus = "EVENTS.EVENTS.STATUS.SCHEDULED";
927     } else {
928       /* This can be the case if all workflows of an event have been deleted */
929       /* It also happens to events with running workflows while rebuilding the index */
930       eventStatus = "EVENTS.EVENTS.STATUS.PROCESSED";
931     }
932   }
933 
934   /**
935    * Return the displayable status of this event
936    *
937    * @param customWorkflowStatusMapping
938    *          The mappings used to get the displayable status for the workflow state.
939    *
940    * @return the displayable status of this event
941    */
942   public String getDisplayableStatus(Map<String, Map<String, String>> customWorkflowStatusMapping) {
943     if (getWorkflowId() != null && StringUtils.isNotBlank(getWorkflowState())
944           && customWorkflowStatusMapping.containsKey(getWorkflowDefinitionId())
945           && customWorkflowStatusMapping.get(getWorkflowDefinitionId()).containsKey(getWorkflowState())) {
946       return customWorkflowStatusMapping.get(getWorkflowDefinitionId()).get(getWorkflowState());
947     }
948     return getEventStatus();
949   }
950 
951   public boolean isScheduledEvent() {
952     /* Only scheduled events have a capture ID assigned */
953     return StringUtils.isNotBlank(getAgentId());
954   }
955 
956   /**
957    * Check whether the recording of the event already has started.
958    * Always returns false for uploaded events.
959    *
960    * @return <code>true</code> if recording of this event has started, and <code>false</code> otherwise
961    */
962   public boolean hasRecordingStarted() {
963     return isScheduledEvent() && StringUtils.isNotBlank(getRecordingStatus());
964   }
965 
966   /**
967    * Sets the recording status
968    *
969    * @param recordingStatus
970    *          the recording status
971    */
972   public void setRecordingStatus(String recordingStatus) {
973     this.recordingStatus = recordingStatus;
974     updateEventStatus();
975   }
976 
977   /**
978    * Returns the recording status
979    *
980    * @return the recording status
981    */
982   public String getRecordingStatus() {
983     return recordingStatus;
984   }
985 
986   /**
987    * Returns the event status
988    *
989    * @return the event status
990    */
991   public String getEventStatus() {
992     updateEventStatus();
993     return eventStatus;
994   }
995 
996   /**
997    * Returns the agent id
998    *
999    * @return the agent id
1000    */
1001   public String getAgentId() {
1002     return agentId;
1003   }
1004 
1005   /**
1006    * Sets the agent id
1007    *
1008    * @param agentId
1009    *          the agent id
1010    */
1011   public void setAgentId(String agentId) {
1012     this.agentId = agentId;
1013   }
1014 
1015   /**
1016    * Returns the agent configuration
1017    *
1018    * @return the agent configuration
1019    */
1020   public Map<String, String> getAgentConfiguration() {
1021     return agentConfigurations;
1022   }
1023 
1024   /**
1025    * Sets the agent configuration
1026    *
1027    * @param agentConfigurations
1028    *          the agent configuration
1029    */
1030   public void setAgentConfiguration(Map<String, String> agentConfigurations) {
1031     this.agentConfigurations = agentConfigurations;
1032   }
1033 
1034   /**
1035    * Returns the technical end time
1036    *
1037    * @return the technical end time
1038    */
1039   public String getTechnicalEndTime() {
1040     return technicalEndTime;
1041   }
1042 
1043   /**
1044    * Sets the technical end time
1045    *
1046    * @param technicalEndTime
1047    *          the technical end time
1048    */
1049   public void setTechnicalEndTime(String technicalEndTime) {
1050     this.technicalEndTime = technicalEndTime;
1051   }
1052 
1053   /**
1054    * Returns the technical start time
1055    *
1056    * @return the technical start time
1057    */
1058   public String getTechnicalStartTime() {
1059     return technicalStartTime;
1060   }
1061 
1062   /**
1063    * Sets the technical start time
1064    *
1065    * @param technicalStartTime
1066    *          the technical start time
1067    */
1068   public void setTechnicalStartTime(String technicalStartTime) {
1069     this.technicalStartTime = technicalStartTime;
1070   }
1071 
1072   /**
1073    * Returns the technical presenters
1074    *
1075    * @return the technical presenters
1076    */
1077   public List<String> getTechnicalPresenters() {
1078     return technicalPresenters;
1079   }
1080 
1081   /**
1082    * Sets the technical presenters
1083    *
1084    * @param technicalPresenters
1085    *          the technical presenters
1086    */
1087   public void setTechnicalPresenters(List<String> technicalPresenters) {
1088     this.technicalPresenters = technicalPresenters;
1089   }
1090 
1091   /**
1092    * Sets the external metadata for a catalog flavor.
1093    *
1094    * @param type
1095    *         The catalog type
1096    * @param metadata
1097    *         The metadata
1098    */
1099   public void setExtendedMetadata(String type, Map<String, List<String>> metadata) {
1100     extendedMetadata.put(type, metadata);
1101   }
1102 
1103   /**
1104    * Removes all external metadata.
1105    */
1106   public void resetExtendedMetadata() {
1107     extendedMetadata.clear();
1108   }
1109 
1110   /**
1111    * Returns the extended metadata
1112    *
1113    * @return the extended metadata in a map by catalog type
1114    */
1115   public Map<String, Map<String, List<String>>> getExtendedMetadata() {
1116     return extendedMetadata;
1117   }
1118 
1119   /**
1120    * Reads the recording event from the input stream.
1121    *
1122    * @param xml
1123    *          the input stream
1124    * @param unmarshaller the unmarshaller to use
1125    * @return the deserialized recording event
1126    * @throws IOException
1127    */
1128   public static Event valueOf(InputStream xml, Unmarshaller unmarshaller) throws IOException {
1129     try {
1130       if (context == null) {
1131         createJAXBContext();
1132       }
1133       return unmarshaller.unmarshal(XmlSafeParser.parse(xml), Event.class).getValue();
1134     } catch (JAXBException e) {
1135       throw new IOException(e.getLinkedException() != null ? e.getLinkedException() : e);
1136     } catch (SAXException e) {
1137       throw new IOException(e);
1138     } finally {
1139       IoSupport.closeQuietly(xml);
1140     }
1141   }
1142 
1143   /**
1144    * Reads the recording event from the input stream.
1145    *
1146    * @param json
1147    *          the input stream
1148    * @return the deserialized recording event
1149    * @throws JSONException
1150    * @throws XMLStreamException
1151    * @throws JAXBException
1152    */
1153   public static Event valueOfJson(InputStream json)
1154           throws IOException, JSONException, XMLStreamException, JAXBException {
1155     // TODO Get this to work, it is currently returning null properties for all properties.
1156     if (context == null) {
1157       createJAXBContext();
1158     }
1159 
1160     BufferedReader streamReader = new BufferedReader(new InputStreamReader(json, "UTF-8"));
1161     StringBuilder jsonStringBuilder = new StringBuilder();
1162     String inputStr;
1163     while ((inputStr = streamReader.readLine()) != null) {
1164       jsonStringBuilder.append(inputStr);
1165     }
1166 
1167     JSONObject obj = new JSONObject(jsonStringBuilder.toString());
1168     Configuration config = new Configuration();
1169     config.setSupressAtAttributes(true);
1170     Map<String, String> xmlToJsonNamespaces = new HashMap<String, String>(1);
1171     xmlToJsonNamespaces.put(IndexObject.INDEX_XML_NAMESPACE, "");
1172     config.setXmlToJsonNamespaces(xmlToJsonNamespaces);
1173     MappedNamespaceConvention con = new MappedNamespaceConvention(config);
1174     Unmarshaller unmarshaller = context.createUnmarshaller();
1175     // CHECKSTYLE:OFF
1176     // the xml is parsed from json and should be safe
1177     XMLStreamReader xmlStreamReader = new MappedXMLStreamReader(obj, con);
1178     Event event = (Event) unmarshaller.unmarshal(xmlStreamReader);
1179     // CHECKSTYLE:ON
1180     return event;
1181   }
1182 
1183   /**
1184    * Initialize the JAXBContext.
1185    */
1186   private static void createJAXBContext() throws JAXBException {
1187     context = JAXBContext.newInstance(Event.class);
1188   }
1189 
1190   /**
1191    * Serializes the recording event.
1192    *
1193    * @return the serialized recording event
1194    */
1195   public String toJSON() {
1196     try {
1197       if (context == null) {
1198         createJAXBContext();
1199       }
1200       Marshaller marshaller = Event.context.createMarshaller();
1201 
1202       Configuration config = new Configuration();
1203       config.setSupressAtAttributes(true);
1204       MappedNamespaceConvention con = new MappedNamespaceConvention(config);
1205       StringWriter writer = new StringWriter();
1206       XMLStreamWriter xmlStreamWriter = new MappedXMLStreamWriter(con, writer) {
1207         @Override
1208         public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
1209           super.writeStartElement("", local, "");
1210         }
1211 
1212         @Override
1213         public void writeStartElement(String uri, String local) throws XMLStreamException {
1214           super.writeStartElement("", local, "");
1215         }
1216 
1217         @Override
1218         public void setPrefix(String pfx, String uri) throws XMLStreamException {
1219         }
1220 
1221         @Override
1222         public void setDefaultNamespace(String uri) throws XMLStreamException {
1223         }
1224       };
1225 
1226       marshaller.marshal(this, xmlStreamWriter);
1227       return writer.toString();
1228     } catch (JAXBException e) {
1229       throw new IllegalStateException(e.getLinkedException() != null ? e.getLinkedException() : e);
1230     }
1231   }
1232 
1233   /**
1234    * Serializes the recording event to an XML format.
1235    *
1236    * @return A String with this event's content as XML.
1237    */
1238   public String toXML() {
1239     try {
1240       if (context == null) {
1241         createJAXBContext();
1242       }
1243       StringWriter writer = new StringWriter();
1244       Marshaller marshaller = Event.context.createMarshaller();
1245       marshaller.marshal(this, writer);
1246       return writer.toString();
1247     } catch (JAXBException e) {
1248       throw new IllegalStateException(e.getLinkedException() != null ? e.getLinkedException() : e);
1249     }
1250   }
1251 
1252   /**
1253    * Create an unmarshaller for events
1254    * @return an unmarshaller for events
1255    * @throws IOException
1256    */
1257   public static Unmarshaller createUnmarshaller() throws IOException {
1258     try {
1259       if (context == null) {
1260         createJAXBContext();
1261       }
1262       return context.createUnmarshaller();
1263     } catch (JAXBException e) {
1264       throw new IOException(e.getLinkedException() != null ? e.getLinkedException() : e);
1265     }
1266   }
1267 
1268 }