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.scheduler.impl;
23  
24  import org.opencastproject.mediapackage.MediaPackage;
25  import org.opencastproject.metadata.dublincore.DublinCore;
26  import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
27  import org.opencastproject.security.api.UnauthorizedException;
28  import org.opencastproject.series.api.SeriesException;
29  import org.opencastproject.series.api.SeriesService;
30  import org.opencastproject.util.NotFoundException;
31  
32  import net.fortuna.ical4j.model.Calendar;
33  import net.fortuna.ical4j.model.DateTime;
34  import net.fortuna.ical4j.model.ParameterList;
35  import net.fortuna.ical4j.model.component.VEvent;
36  import net.fortuna.ical4j.model.parameter.Encoding;
37  import net.fortuna.ical4j.model.parameter.FmtType;
38  import net.fortuna.ical4j.model.parameter.Value;
39  import net.fortuna.ical4j.model.parameter.XParameter;
40  import net.fortuna.ical4j.model.property.Attach;
41  import net.fortuna.ical4j.model.property.CalScale;
42  import net.fortuna.ical4j.model.property.Description;
43  import net.fortuna.ical4j.model.property.LastModified;
44  import net.fortuna.ical4j.model.property.Location;
45  import net.fortuna.ical4j.model.property.ProdId;
46  import net.fortuna.ical4j.model.property.RelatedTo;
47  import net.fortuna.ical4j.model.property.Uid;
48  import net.fortuna.ical4j.model.property.Version;
49  
50  import org.apache.commons.lang3.StringUtils;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  import java.io.IOException;
55  import java.util.Date;
56  import java.util.HashMap;
57  import java.util.Map;
58  
59  /**
60   * Create an iCalendar from the provided scheduled events.
61   */
62  public class CalendarGenerator {
63  
64    /** Logging utility */
65    private static final Logger logger = LoggerFactory.getLogger(CalendarGenerator.class);
66  
67    /** iCalendar */
68    protected Calendar cal;
69  
70    /** Series service for Series DC retrieval */
71    protected SeriesService seriesService;
72  
73    private final Map<String, DublinCoreCatalog> series = new HashMap<>();
74  
75    /**
76     * Default constructor that creates a CalendarGenerator object
77     *
78     * @param seriesService
79     *          the series service
80     */
81    public CalendarGenerator(SeriesService seriesService) {
82      cal = new Calendar();
83      cal.getProperties().add(new ProdId("Opencast Calendar File 0.5"));
84      cal.getProperties().add(Version.VERSION_2_0);
85      cal.getProperties().add(CalScale.GREGORIAN);
86      this.seriesService = seriesService;
87    }
88  
89    /**
90     * gets the iCalendar creates by this object.
91     *
92     * @return the iCalendar
93     */
94    public Calendar getCalendar() {
95      return cal;
96    }
97  
98    /**
99     * Sets an iCalender to work with
100    *
101    * @param cal
102    *          the iCalendar to set
103    */
104   public void setCalendar(Calendar cal) {
105     this.cal = cal;
106   }
107 
108   /**
109    * Adds an SchedulerEvent as a new entry to this iCalendar
110    *
111    * @param mp
112    *          {@link MediaPackage} of event
113    * @param agentId
114    *          the agent identifier
115    * @param start
116    *          the start date
117    * @param end
118    *          the end date
119    * @param captureAgentMetadata
120    *          properties for capture agent metadata
121    *
122    * @return true if the event could be added.
123    */
124   public boolean addEvent(MediaPackage mp, DublinCoreCatalog catalog, String agentId, Date start, Date end,
125           Date lastModified, String captureAgentMetadata) {
126     String eventId = mp.getIdentifier().toString();
127 
128     logger.debug("Creating iCalendar VEvent from scheduled event '{}'", eventId);
129 
130     DateTime startDate = new DateTime(start);
131     DateTime endDate = new DateTime(end);
132     Date marginEndDate = new org.joda.time.DateTime(endDate.getTime()).plusHours(1).toDate();
133     if (marginEndDate.before(new Date())) {
134       logger.debug("Event has already passed more than an hour, skipping!");
135       return false;
136     }
137     startDate.setUtc(true);
138     endDate.setUtc(true);
139     String seriesID = null;
140 
141     VEvent event = new VEvent(startDate, endDate, catalog.getFirst(DublinCore.PROPERTY_TITLE));
142     try {
143       event.getProperties().add(new Uid(eventId));
144 
145       DateTime lastModifiedDate = new DateTime(lastModified);
146       lastModifiedDate.setUtc(true);
147       event.getProperties().add(new LastModified(lastModifiedDate));
148 
149       if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_DESCRIPTION))) {
150         event.getProperties().add(new Description(catalog.getFirst(DublinCore.PROPERTY_DESCRIPTION)));
151       }
152       event.getProperties().add(new Location(agentId));
153       if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_IS_PART_OF))) {
154         seriesID = catalog.getFirst(DublinCore.PROPERTY_IS_PART_OF);
155         event.getProperties().add(new RelatedTo(seriesID));
156       }
157 
158       ParameterList dcParameters = new ParameterList();
159       dcParameters.add(new FmtType("application/xml"));
160       dcParameters.add(Value.BINARY);
161       dcParameters.add(Encoding.BASE64);
162       dcParameters.add(new XParameter("X-APPLE-FILENAME", "episode.xml"));
163       Attach metadataAttachment = new Attach(dcParameters, catalog.toXmlString().getBytes("UTF-8"));
164       event.getProperties().add(metadataAttachment);
165 
166       String seriesDC = getSeriesDublinCoreAsString(seriesID);
167       if (seriesDC != null) {
168         logger.debug("Attaching series {} information to event {}", seriesID, eventId);
169         ParameterList sDcParameters = new ParameterList();
170         sDcParameters.add(new FmtType("application/xml"));
171         sDcParameters.add(Value.BINARY);
172         sDcParameters.add(Encoding.BASE64);
173         sDcParameters.add(new XParameter("X-APPLE-FILENAME", "series.xml"));
174         Attach seriesAttachment = new Attach(sDcParameters, seriesDC.getBytes("UTF-8"));
175         event.getProperties().add(seriesAttachment);
176       } else {
177         logger.debug("No series provided for event {}.", eventId);
178       }
179 
180       ParameterList caParameters = new ParameterList();
181       caParameters.add(new FmtType("application/text"));
182       caParameters.add(Value.BINARY);
183       caParameters.add(Encoding.BASE64);
184       caParameters.add(new XParameter("X-APPLE-FILENAME", "org.opencastproject.capture.agent.properties"));
185       Attach agentsAttachment = new Attach(caParameters, captureAgentMetadata.getBytes("UTF-8"));
186       event.getProperties().add(agentsAttachment);
187 
188     } catch (Exception e) {
189       logger.error("Unable to add event '{}' to recording calendar", eventId, e);
190       return false;
191     }
192 
193     cal.getComponents().add(event);
194 
195     logger.debug("new VEvent = {} ", event.toString());
196     return true;
197   }
198 
199   /**
200    * Returns series DC associated with this event or null if {@link SeriesService} is not available or does not contain
201    * entry for series with specified ID.
202    *
203    * @param seriesID
204    *          {@link DublinCoreCatalog} to be retrieved
205    * @return DC serialized to string or null
206    * @throws UnauthorizedException
207    *           if the current user is not allowed to view this series
208    * @throws NotFoundException
209    *           if the series cannot be found
210    */
211   private String getSeriesDublinCoreAsString(String seriesID) throws UnauthorizedException, NotFoundException {
212     if (StringUtils.isBlank(seriesID)) {
213       return null;
214     }
215     if (seriesService == null) {
216       logger.warn("No SeriesService available");
217       return null;
218     }
219 
220     DublinCoreCatalog seriesDC = series.get(seriesID);
221     if (seriesDC == null) {
222       try {
223         seriesDC = seriesService.getSeries(seriesID);
224         series.put(seriesID, seriesDC);
225       } catch (SeriesException e) {
226         logger.error("Error loading DublinCoreCatalog for series '{}'", seriesID, e);
227         return null;
228       }
229     }
230 
231     try {
232       return seriesDC.toXmlString();
233     } catch (IOException e) {
234       logger.error("Error serializing DublinCoreCatalog of series '{}'", seriesID, e);
235       return null;
236     }
237   }
238 
239 }